Error library

Supabase Auth: Invalid Refresh Token

Supabase Auth returns this error (code refresh_token_not_found, HTTP 400) when your app tries to refresh a session with a refresh token that Auth no longer accepts. Refresh tokens can only be used once. A session also ends when the user signs out, changes their password, times out, or signs in elsewhere with single-session mode on. Users see random logouts, and the error often shows up in server or middleware logs.

Cause: The stored refresh token was already used by another request (two refreshes racing each other), its session was ended, or the app tried to refresh when nobody was signed in.

How to fix it

  1. Update @supabase/supabase-js and @supabase/ssr. Supabase maintainers said a middleware refresh race was fixed in @supabase/ssr 0.8.1.
  2. In Next.js, refresh the session in one place: middleware calls supabase.auth.getUser() and writes the new cookies to both the request and the response, as in Supabase's server-side auth guide.
  3. Don't call getUser() or getSession() again in a route handler for the same request.
  4. Create one browser client and reuse it, instead of a new client in every component.
  5. Check sign-out: signOut() ends the user's sessions on all devices by default. Use signOut({ scope: 'local' }) to sign out only the current device.
  6. Check Auth settings for time-boxed sessions, an inactivity timeout or single session per user.
  7. When a refresh fails, clear the local session and send the user to sign in, instead of retrying in a loop.

Example

Illustrative example, not from a customer project.

- // a new client in every component, each one refreshing on its own
- const supabase = createClient(url, key)
+ // lib/supabase.ts: one shared browser client
+ export const supabase = createClient(url, key)

- await supabase.auth.signOut()                   // signs out every device
+ await supabase.auth.signOut({ scope: 'local' }) // signs out this device only

FAQ

Is "Invalid Refresh Token: Already Used" the same problem?

Yes, same family. A refresh token was reused outside Supabase's short reuse window (10 seconds by default), so Auth treats the session as compromised and revokes it.

Why does it happen when nobody is logged in?

Something tried to refresh a session that doesn't exist. Supabase maintainers suggest calling getUser() first and skipping the protected query when there is no user.

Can I turn off refresh token reuse detection?

There is a setting in the Auth advanced settings, but Supabase does not recommend it. Fix the double refresh instead.

I self-host Supabase. Anything different?

Supabase maintainers say self-hosted projects have the reuse window off by default. Set GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL to a few seconds.

Get an auth & database security fix, from $400 Not sure? Get a $149 diagnosis

Built with Lovable, Bolt, Replit, Cursor or v0? See rescue by platform and the security checklist.

Check it yourself first, free

$149Get a $149 diagnosis