The PKCE flow state expires after 5 minutes of inactivity. This error occurs when users take too long to complete the OAuth authentication callback or attempt to use an expired authorization code. Complete the authentication flow within 5 minutes or restart the login process to fix this.
The "PKCE flow state has expired" error occurs when the OAuth 2.0 PKCE (Proof Key for Code Exchange) authentication code or state parameter becomes invalid due to timeout. In Supabase, PKCE flow state is designed to expire after 5 minutes for security reasons. The authorization code can only be exchanged for an access token once and must be completed within this 5-minute window. If a user is redirected to the callback URL but the code exchange happens after the timeout, or if they attempt to reuse an already-exchanged code, Supabase rejects the request with this error. This is a security measure to protect against code interception attacks and unauthorized token exchanges. Unlike regular session timeouts, this error specifically indicates that the temporary authorization code used in the OAuth handshake has become invalid.
The PKCE authorization code expires 5 minutes after the OAuth flow is initiated. Ensure users complete the entire process (click link, authorize, callback) within this window.
If using magic links, advise users to:
- Click the authentication link promptly after requesting it
- Complete any required authorization steps immediately
- Not leave the page idle during the callback process
If the error occurs, the authorization code has expired and cannot be reused. Restart the entire login process:
// User initiates login again
await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});For magic links:
await supabase.auth.signInWithOtp({
email: '[email protected]',
});
// User receives new magic link and must click it promptlyEnsure your OAuth redirect URL is correctly configured in Supabase and matches your application:
1. Go to Supabase dashboard > Authentication > URL Configuration
2. Verify "Redirect URLs" includes your callback URL exactly as it appears in your app
3. For local development: http://localhost:3000/auth/callback
4. For production: https://yourdomain.com/auth/callback
Misconfigured URLs can cause unnecessary delays reaching the callback endpoint.
For OAuth providers (Google, GitHub, etc.), also verify:
- OAuth client ID and secret are correct
- Authorized redirect URIs include your Supabase callback URL
If building a server-side rendered (SSR) application, use the @supabase/ssr package which handles PKCE flow and session management automatically:
npm install @supabase/ssrimport { createServerClient } from '@supabase/ssr';
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
getAll() { /* ... */ },
setAll() { /* ... */ },
},
}
);This package:
- Uses PKCE flow by default
- Automatically handles state parameter storage in cookies
- Manages session tokens securely
- Handles callback exchange server-side
When handling the OAuth callback, ensure you exchange the code within the 5-minute window:
// In your callback route handler (/auth/callback)
const { data, error } = await supabase.auth.exchangeCodeForSession(code);
if (error) {
// Log specific error
console.error('Code exchange failed:', error.message);
// Redirect user back to login
return redirect('/login');
}
// Session is now valid, redirect to dashboard
return redirect('/dashboard');Ensure:
- The callback handler executes immediately upon redirect
- No delays or middleware interferes with code exchange
- The code is extracted from the URL query parameter and passed correctly
Understanding PKCE State Management: PKCE (Proof Key for Code Exchange) improves OAuth security by introducing a code verifier (randomly generated secret) and code challenge (hash of the verifier). Supabase stores the state parameter in the browser (localStorage or cookies for SSR) and validates it when the code is exchanged. If the browser is closed or cookies are cleared, the state is lost. For same-device flows (recommended for SPAs), the code verifier must be available on the same browser instance that initiated the flow. Network Considerations: In high-latency environments or with slow APIs, reaching the callback endpoint quickly becomes critical. If your callback handler has expensive operations (database lookups, external API calls), defer these until after the code exchange completes. For email magic links with delays in sending, consider increasing user communication about the 5-minute window. Cookie vs LocalStorage: For SSR applications, Supabase recommends storing session data in cookies (via @supabase/ssr), which are automatically sent with requests and survive page refreshes. LocalStorage is only suitable for client-side SPAs where the same JavaScript instance handles the callback. Device/Browser Mismatch: The PKCE flow requires the same device and browser throughout. If a user clicks a link on mobile and completes auth on desktop (or vice versa), the code verifier won't be available. This is intentional security behavior but may confuse users sharing links.
email_address_not_authorized: Email sending to this address is not authorized
Email address not authorized for sending in Supabase Auth
reauthentication_needed: Reauthentication required for security-sensitive actions
Reauthentication required for security-sensitive actions
no_authorization: No authorization header was provided
How to fix "no authorization header was provided" in Supabase
otp_expired: OTP has expired
How to fix 'otp_expired: OTP has expired' in Supabase
bad_oauth_state: OAuth state parameter is missing or invalid
How to fix 'bad_oauth_state: OAuth state parameter missing' in Supabase