rotateRefreshToken method

Future<TokenPair> rotateRefreshToken(
  1. Session session, {
  2. required String refreshToken,
  3. Transaction? transaction,
})

Returns a new refresh / access token pair.

This invalidates the previous refresh token. Previously created access tokens for this refresh token will continue to work until they expire.

Implementation

Future<TokenPair> rotateRefreshToken(
  final Session session, {
  required final String refreshToken,
  final Transaction? transaction,
}) async {
  final RefreshTokenStringData refreshTokenData;

  try {
    refreshTokenData = RefreshTokenString.parseRefreshTokenString(
      refreshToken,
    );
  } catch (e, stackTrace) {
    session.log(
      'Received malformed refresh token',
      exception: e,
      stackTrace: stackTrace,
      level: LogLevel.debug,
    );

    throw RefreshTokenMalformedServerException();
  }

  var refreshTokenRow = await RefreshToken.db.findById(
    session,
    refreshTokenData.id,
    transaction: transaction,
  );

  if (refreshTokenRow == null ||
      !uint8ListAreEqual(
        Uint8List.sublistView(refreshTokenRow.fixedSecret),
        refreshTokenData.fixedSecret,
      )) {
    throw RefreshTokenNotFoundServerException();
  }

  if (refreshTokenRow.isExpired(_refreshTokenLifetime)) {
    await RefreshToken.db.deleteRow(
      session,
      refreshTokenRow,
      transaction: transaction,
    );

    throw RefreshTokenExpiredServerException(
      refreshTokenId: refreshTokenRow.id!,
      authUserId: refreshTokenRow.authUserId,
    );
  }

  if (!await _refreshTokenSecretHash.validateHashFromBytes(
    secret: refreshTokenData.rotatingSecret,
    hashString: refreshTokenRow.rotatingSecretHash,
  )) {
    await RefreshToken.db.deleteRow(
      session,
      refreshTokenRow,
      transaction: transaction,
    );

    throw RefreshTokenInvalidSecretServerException(
      refreshTokenId: refreshTokenRow.id!,
      authUserId: refreshTokenRow.authUserId,
    );
  }

  final newSecret = _generateRefreshTokenRotatingSecret();
  final newHash = await _refreshTokenSecretHash.createHashFromBytes(
    secret: newSecret,
  );

  refreshTokenRow = await RefreshToken.db.updateRow(
    session,
    refreshTokenRow.copyWith(
      rotatingSecretHash: newHash,
      lastUpdatedAt: clock.now(),
    ),
    transaction: transaction,
  );

  return TokenPair(
    refreshToken: RefreshTokenString.buildRefreshTokenString(
      refreshToken: refreshTokenRow,
      rotatingSecret: newSecret,
    ),
    accessToken: _jwtUtil.createJwt(refreshTokenRow),
  );
}