refreshAccessToken static method
Exchange a refresh token for a new access token and optional rotated refresh token.
Returns null when refresh token is invalid/expired/revoked.
Implementation
static Future<Map<String, dynamic>?> refreshAccessToken(
String refreshToken, {
bool rotateRefreshToken = true,
String? ipAddress,
String? userAgent,
String? deviceName,
}) async {
if (!_config.enableRefreshTokens) return null;
await ensureFrameworkTablesExist();
final tokenHash = _hashRefreshToken(refreshToken);
final rows = await DB.query(
'SELECT id, user_id, expires_at, revoked_at FROM $_refreshTokensTable WHERE token_hash = ? LIMIT 1',
positionalParams: [tokenHash],
);
if (rows.isEmpty) return null;
final row = rows.first;
if (row['revoked_at'] != null) return null;
final expiresAtRaw = row['expires_at']?.toString();
if (expiresAtRaw == null) return null;
final expiresAt = DateTime.tryParse(expiresAtRaw);
if (expiresAt == null || expiresAt.isBefore(DateTime.now())) {
return null;
}
final userId = row['user_id']?.toString();
if (userId == null || userId.isEmpty) return null;
final user = await QueryBuilder(table: _config.table)
.where('id', '=', userId)
.limit(1)
.first();
if (user == null) return null;
final cleanUser = _sanitizeUserData(user);
final accessToken = _issueAccessToken(cleanUser);
if (!rotateRefreshToken) {
return {
'user': cleanUser,
'accessToken': accessToken,
'token': accessToken,
};
}
await DB.query(
'UPDATE $_refreshTokensTable SET revoked_at = ? WHERE id = ?',
positionalParams: [DateTime.now().toIso8601String(), row['id']],
);
final newRefreshToken = await _createRefreshTokenRecord(
userId: userId,
ipAddress: ipAddress,
userAgent: userAgent,
deviceName: deviceName,
);
return {
'user': cleanUser,
'accessToken': accessToken,
'token': accessToken,
'refreshToken': newRefreshToken,
};
}