Skip to content

fix(auth): proactively refresh expired tokens on app resume#1310

Open
grdsdev wants to merge 1 commit intomainfrom
fix/proactive-token-refresh-on-resume
Open

fix(auth): proactively refresh expired tokens on app resume#1310
grdsdev wants to merge 1 commit intomainfrom
fix/proactive-token-refresh-on-resume

Conversation

@grdsdev
Copy link
Contributor

@grdsdev grdsdev commented Feb 5, 2026

Fixes #452
Fixes #906
Fixes #1158

Problem

When the app resumes from background with an expired access token, there's a race condition where API calls made before the first auto-refresh tick (which runs every 10 seconds) use the expired token, resulting in:

This is one of the highest-impact issues in the supabase-flutter SDK, affecting 100+ users based on GitHub issue engagement.

Root Cause

In didChangeAppLifecycleState(AppLifecycleState.resumed), the code starts the auto-refresh ticker without checking if the current session is already expired:

case AppLifecycleState.resumed:
  if (_autoRefreshToken) {
    Supabase.instance.client.auth.startAutoRefresh();  // ❌ No expiry check
  }

The auto-refresh ticker runs every 10 seconds (Constants.autoRefreshTickDuration). If an API call happens during this window, it uses the expired token.

Timeline of the bug:

  1. App goes to background → auto-refresh stops
  2. App stays in background > 1 hour → token expires
  3. User resumes app → startAutoRefresh() is called
  4. 0-10 seconds: Token is expired but not yet refreshed
  5. API call happens → uses expired token → PGRST301 error
  6. User is logged out unexpectedly

Solution

Check if the session is expired when the app resumes and proactively trigger a refresh BEFORE starting the auto-refresh ticker:

case AppLifecycleState.resumed:
  _log.fine('App resumed, checking session status');

  // ✅ Proactively refresh if token is expired
  final session = Supabase.instance.client.auth.currentSession;
  if (session != null && session.isExpired) {
    _log.fine('Session expired on resume, refreshing proactively');
    unawaited(
      Supabase.instance.client.auth.refreshSession().catchError((error) {
        _log.warning('Proactive refresh on resume failed', error);
        return null;
      }),
    );
  }

  if (_autoRefreshToken) {
    Supabase.instance.client.auth.startAutoRefresh();
  }

The refresh happens in the background (unawaited) to avoid blocking app resume. Errors are handled gracefully via the onAuthStateChange listener.

Changes

File: packages/supabase_flutter/lib/src/supabase_auth.dart

  • ✅ Added session expiry check on app resume (line 118-133)
  • ✅ Proactive refreshSession() call if token is expired
  • ✅ Error handling with fallback to onAuthStateChange listener
  • ✅ Added unawaited import from dart:async
  • ✅ Added debug logging for troubleshooting

Test Plan

  • ✅ All existing tests pass (36/36 tests)
  • ✅ No breaking changes to public API
  • ✅ Backwards compatible

Manual testing scenarios:

  1. Background app for >1 hour (token expires)
  2. Resume app
  3. Make API call immediately
  4. Expected: Token refreshes proactively, no PGRST301 errors
  5. Before fix: PGRST301 error, user logged out

Related Issues

This PR complements #1309 (TokenRefreshHandler refactor):

Together, these PRs resolve the top auth token refresh issues in the repository.

Impact

Resolves 106 combined comments across 3 high-priority issues:

Breaking Changes

None. This is a non-breaking bug fix.


🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Enhanced session management with proactive refresh when the app resumes, improving session continuity.

Fixes #452, #906, #1158

## Problem
When the app resumes from background with an expired access token, there's
a race condition where API calls made before the first auto-refresh tick
(which runs every 10 seconds) use the expired token, resulting in:
- PGRST301 "JWT expired" errors
- AuthRetryableFetchException
- Unexpected user logouts

## Root Cause
In didChangeAppLifecycleState(AppLifecycleState.resumed), the code starts
the auto-refresh ticker without checking if the current session is already
expired. The ticker takes up to 10 seconds to detect expiry and trigger a
refresh, leaving a window where expired tokens are used.

## Solution
Check if the session is expired when the app resumes and proactively
trigger a refresh BEFORE starting the auto-refresh ticker. The refresh
happens in the background (unawaited) to avoid blocking app resume.

## Changes
- Added session expiry check on app resume
- Proactive refreshSession() call if token is expired
- Error handling with fallback to onAuthStateChange listener
- Added logging for debugging

## Test Plan
- All existing tests pass (36/36)
- Manual testing: backgrounding app for >1 hour, resuming, making API calls
- Expected: no PGRST301 errors, seamless token refresh

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@github-actions github-actions bot added the auth This issue or pull request is related to authentication label Feb 5, 2026
@grdsdev
Copy link
Contributor Author

grdsdev commented Feb 5, 2026

How This PR Works With #1309

This PR is complementary to #1309 (TokenRefreshHandler refactor). Together, they fix the major auth token refresh issues:

PR Fixes Root Cause
#1309 #895, #930, (partial #928) Concurrent refresh race conditions
This PR (#1310) #452, #906, #1158 App lifecycle race condition

Combined Impact

When both PRs are merged:

Why Both Are Needed

#1309 makes the refresh mechanism robust against concurrent requests, but doesn't fix the timing issue when the app resumes.

This PR adds the proactive refresh check that prevents expired tokens from being used during the 0-10 second window after app resume.

Test Plan

To fully test both fixes:

  1. Test feat(auth): extract token refresh logic into TokenRefreshHandler #1309: Multiple concurrent API calls with token refresh
  2. Test this PR: Background app >1 hour, resume, make API call immediately
  3. Test both: Background app, resume, make multiple concurrent API calls

cc: @grdsdev

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

Modified supabase_auth.dart to narrow imports and implement proactive session refresh on app lifecycle resume. When the app resumes, the code checks for an expired session and triggers a background refresh using refreshSession, with errors logged and handled through the existing onAuthStateChange listener.

Changes

Cohort / File(s) Summary
Session Refresh on App Resume
packages/supabase_flutter/lib/src/supabase_auth.dart
Narrowed dart:async imports to StreamSubscription and unawaited. Added logic to app lifecycle resume handler to proactively refresh an expired session via refreshSession without awaiting, with verbose logging and error handling delegated to onAuthStateChange listener. Preserved existing auto-refresh behavior when enabled.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: adding proactive token refresh on app resume to fix expired token issues.
Linked Issues check ✅ Passed The PR addresses core objectives from all three linked issues: #452 (handling expired JWT), #906 (token refresh on app open), and #1158 (auto-refresh reliability).
Out of Scope Changes check ✅ Passed All changes are directly scoped to the auth token refresh fix; narrowing the import and adding proactive session refresh in didChangeAppLifecycleState are both necessary for the stated objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/proactive-token-refresh-on-resume

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth This issue or pull request is related to authentication

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto Refresh is not working Supabase.initialize don't refresh token after 1h on app open again onAuthStateChange not triggered when JWT expired

1 participant