• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / supabase-flutter / 27286835568
85%

Build:
DEFAULT BRANCH: main
Ran 10 Jun 2026 03:31PM UTC
Jobs 1
Files 72
Run time 1min
Badge
Embed ▾
README BADGES
x

If you need to use a raster PNG badge, change the '.svg' to '.png' in the link

Markdown

Textile

RDoc

HTML

Rst

10 Jun 2026 03:24PM UTC coverage: 80.78% (+0.2%) from 80.615%
27286835568

push

github

web-flow
fix(auth): avoid reusing a stale refresh token in recoverSession (#1391)

Fixes #452
Fixes #906
Fixes #1158

## Problem

On cold start, users are intermittently signed out with:

```
AuthApiException(message: Invalid Refresh Token: Already Used, statusCode: 400, code: refresh_token_already_used)
```

The stack trace consistently points to `recoverSession` →
`_callRefreshToken` → `_refreshAccessToken`. Reporters note it happens
on **real devices** but not emulators, and is intermittent, both classic
signs of a race.

## Root cause

On startup `supabase_flutter` runs **two independent refreshes against
the same persisted refresh token** (`RT1`):

1. `SupabaseAuth.recoverSession()` (run as a non-awaited
`CancelableOperation`) → `auth.recoverSession(jsonStr)`, which, for an
expired session, calls `_callRefreshToken(refreshToken)` using the token
**serialized in the JSON string** (`RT1`).
2. The lifecycle `resumed` event → `startAutoRefresh()`, which ticks
immediately and refreshes `_currentSession.refreshToken` (`RT1`).

`_callRefreshToken`'s `_refreshTokenCompleter` guard only deduplicates
*concurrent* calls. If path 2 starts **and finishes first** (obtaining
`RT2`, saving the session, clearing the completer), then path 1 later
calls `_callRefreshToken(RT1)` with the now-stale token. Since refresh
tokens are single-use, the server rejects it as
`refresh_token_already_used`, and `_callRefreshToken` reacts to that
non-retryable error by removing the session and emitting `signedOut`.
Slower secure-storage reads on real devices widen the window, which is
why it's device-specific and intermittent.

## Why not the previous approach (#1310)

[#1310](https://github.com/supabase/supabase-flutter/pull/1310) tried to
"proactively refresh on resume". As pointed out [in
review](https://github.com/supabase/supabase-flutter/pull/1310#issuecomment-4451181091),
`startAutoRefresh()` already ticks immediately, so that change does
nothing for this bug and merely adds *anoth... (continued)

12 of 12 new or added lines in 1 file covered. (100.0%)

3585 of 4438 relevant lines covered (80.78%)

3.02 hits per line

Jobs
ID Job ID Ran Files Coverage
1 27286835568.1 10 Jun 2026 03:31PM UTC 72
80.78
GitHub Action Run
Source Files on build 27286835568
  • Tree
  • List 72
  • Changed 1
  • Source Changed 1
  • Coverage Changed 1
Coverage ∆ File Lines Relevant Covered Missed Hits/Line
  • Back to Repo
  • Github Actions Build #27286835568
  • de2adf99 on github
  • Prev Build on main (#27268310627)
  • Next Build on main (#27345060038)
  • Delete
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc