Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/gotrue/lib/src/gotrue_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,17 @@ class GoTrueClient {

if (session.isExpired) {
_log.fine('Session from recovery is expired');

// Check if we already have a valid session for the same user.
// This can happen when another code path (e.g., _autoRefreshTokenTick) already refreshed.
if (_currentSession != null &&
_currentSession!.isExpired == false &&
_currentSession!.user.id == session.user.id) {
_log.fine(
'Session from recovery is expired, but current session is valid. Returning current session.');
return AuthResponse(session: _currentSession);
}

final refreshToken = session.refreshToken;
if (_autoRefreshToken && refreshToken != null) {
return await _callRefreshToken(refreshToken);
Expand Down Expand Up @@ -1266,6 +1277,8 @@ class GoTrueClient {
///
/// To prevent multiple simultaneous requests it catches an already ongoing request by using the global [_refreshTokenCompleter].
/// If that's not null and not completed it returns the future of the ongoing request.
///
/// Also handles "refresh_token_already_used" errors gracefully when another refresh already succeeded.
Future<AuthResponse> _callRefreshToken(String refreshToken) async {
// Refreshing is already in progress
if (_refreshTokenCompleter != null) {
Expand Down Expand Up @@ -1297,6 +1310,19 @@ class GoTrueClient {
_refreshTokenCompleter?.complete(data);
return data;
} on AuthException catch (error, stack) {
// Handle "refresh_token_already_used" error gracefully.
// If we have a valid current session, another refresh succeeded - return it instead of signing out.
if (error is AuthApiException &&
error.code == 'refresh_token_already_used') {
if (_currentSession != null && _currentSession!.isExpired == false) {
_log.fine(
'Refresh token was already used, but session is still valid. Returning current session.');
final response = AuthResponse(session: _currentSession);
_refreshTokenCompleter?.complete(response);
return response;
}
}

if (error is! AuthRetryableFetchException) {
_removeSession();
notifyAllSubscribers(AuthChangeEvent.signedOut);
Expand Down
Loading
Loading