fix(auth): proactively refresh expired tokens on app resume#1310
fix(auth): proactively refresh expired tokens on app resume#1310
Conversation
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>
How This PR Works With #1309This PR is complementary to #1309 (TokenRefreshHandler refactor). Together, they fix the major auth token refresh issues:
Combined ImpactWhen 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 PlanTo fully test both fixes:
cc: @grdsdev |
📝 WalkthroughWalkthroughModified Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
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. Comment |
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:
PGRST301 "JWT expired"errors (onAuthStateChange not triggered when JWT expired #452 - 57 comments)AuthRetryableFetchException(Sentry reports AuthException(message: AuthRetryableFetchError, statusCode: null) #928)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: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:
startAutoRefresh()is calledSolution
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. Errors are handled gracefully via theonAuthStateChangelistener.Changes
File:
packages/supabase_flutter/lib/src/supabase_auth.dartrefreshSession()call if token is expiredonAuthStateChangelistenerunawaitedimport fromdart:asyncTest Plan
Manual testing scenarios:
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