rotateRefreshToken static method
Future<TokenPair>
rotateRefreshToken(
- Session session, {
- required String refreshToken,
- 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
static 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 RefreshTokenMalformedException();
}
var refreshTokenRow = await RefreshToken.db.findById(
session,
refreshTokenData.id,
transaction: transaction,
);
if (refreshTokenRow == null ||
!uint8ListAreEqual(
Uint8List.sublistView(refreshTokenRow.fixedSecret),
refreshTokenData.fixedSecret,
)) {
// If the `fixedSecret` does not match, we do not delete the refresh token.
// (Since the `id` is encoded on every access token and thus might be known to 3rd parties or leak, even after the access token itself has expired.)
throw RefreshTokenNotFoundException();
}
if (refreshTokenRow.isExpired) {
await RefreshToken.db.deleteRow(
session,
refreshTokenRow,
transaction: transaction,
);
throw RefreshTokenExpiredException();
}
if (!await _refreshTokenSecretHash.validateHash(
secret: refreshTokenData.rotatingSecret,
hash: Uint8List.sublistView(refreshTokenRow.rotatingSecretHash),
salt: Uint8List.sublistView(refreshTokenRow.rotatingSecretSalt),
)) {
await RefreshToken.db.deleteRow(
session,
refreshTokenRow,
transaction: transaction,
);
throw RefreshTokenInvalidSecretException();
}
final newSecret = _generateRefreshTokenRotatingSecret();
final newHash = await _refreshTokenSecretHash.createHash(secret: newSecret);
refreshTokenRow = await RefreshToken.db.updateRow(
session,
refreshTokenRow.copyWith(
rotatingSecretHash: ByteData.sublistView(newHash.hash),
rotatingSecretSalt: ByteData.sublistView(newHash.salt),
lastUpdatedAt: clock.now(),
),
transaction: transaction,
);
return TokenPair(
refreshToken: RefreshTokenString.buildRefreshTokenString(
refreshToken: refreshTokenRow,
rotatingSecret: newSecret,
),
accessToken: _jwtUtil.createJwt(refreshTokenRow),
);
}