Short answer
Always start social sign-in with signInWithOAuth so Supabase creates the state. On the page the user lands on, read error_code from the URL and show a Sign in again button for bad_oauth_state. Make sure the provider’s callback URL is the callback of this Supabase project, and that users aren’t sent back to an old callback link.
Rather not fix it yourself? App Fix Ticket starts with a fixed-price diagnosis, then a fixed price for the fix before any fix work.
Why Supabase rejects the state, and the fix
01
The sign-in took too long
Each OAuth state expires; the Auth server never sets the limit below five minutes. A user who stops at the provider’s login or 2FA screen comes back to “OAuth state has expired”. Catch the error and restart sign-in.
const params = new URLSearchParams(window.location.search); if (params.get('error_code') === 'bad_oauth_state') { showRetry(() => supabase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: `${location.origin}/auth/callback` }, })); }02
The app made its own state
“OAuth state (data echoed back by the OAuth provider to Supabase Auth) is not in the correct format.” Supabase docs, Auth error codes (bad_oauth_state)
Custom OAuth code that sends the user to Google with its own state value fails, because Supabase only accepts the state ID it created. Let signInWithOAuth build the provider URL.
03
The callback belongs to another project
A staging and a production project share one OAuth client, and the provider sends users to the wrong callback. That project has no record of the state, so it returns “not found or expired”. Use one OAuth client per project, or list each callback URL exactly.
04
The error lands on the Site URL
Also check: when the state fails, Supabase can’t read the redirectTo address saved with it, so users land on the Site URL, or the external redirect URL if one is set. Read the error there too. A missing state returns bad_oauth_callback, and a reused PKCE state returns flow_state_already_used.
Checked against the vendor’s docs on : Supabase docs: Auth error codes; Supabase Auth source: external.go (state checks); Supabase Auth source: configuration.go (flow state expiry); Supabase docs: PKCE flow.
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.See scope and start
“Very responsive, attentive to fixing bugs, and completed the project as needed! Thank you!”
Related: Supabase invalid flow state (code verifier) · Supabase OAuth provider disabled · Google OAuth redirect_uri_mismatch · Supabase PGRST200