How to fix flow_state_not_found: PKCE flow state could not be found in Supabase

SupabaseINTERMEDIATEMEDIUMUpdated Dec 22, 2025ยท 153 views

Supabase returns "flow_state_not_found: PKCE flow state could not be found" when the OAuth authorization flow state is missing, expired, or corrupted. This typically happens when users take too long to complete social login, when browser sessions are cleared mid-flow, or when PKCE code verifiers don't match. The fix involves adjusting flow timeouts, preserving session storage, and ensuring consistent PKCE implementation.

Quick fix

Fixes flow_state_not_found: PKCE flow state could not be found

// Server-side configuration (if self-hosting Supabase)
// In your Supabase Auth configuration or environment variables
AUTH_FLOW_STATE_EXPIRY=900  // 15 minutes in seconds

// For managed Supabase, you may need to contact support
// or check project settings for timeout adjustments
  1. Increase PKCE flow timeout in Supabase Auth configuration
  2. Preserve PKCE code verifier in reliable storage
  3. Implement proper PKCE flow recovery and retry logic
  4. Check for concurrent OAuth flows and race conditions
supabase โ€” error
// Server-side configuration (if self-hosting Supabase)
// In your Supabase Auth configuration or environment variables
AUTH_FLOW_STATE_EXPIRY=900 // 15 minutes in seconds
// For managed Supabase, you may need to contact support
// or check project settings for timeout adjustments
flow_state_not_found: PKCE flow state could not be found

What this error means

The "flow_state_not_found" error occurs during Supabase OAuth authentication when the server cannot locate the Proof Key for Code Exchange (PKCE) flow state that was created at the beginning of the authorization process. PKCE is a security mechanism that prevents authorization code interception attacks by binding the authorization request to the token exchange. When a user initiates OAuth login (e.g., with GitHub, Google, etc.), Supabase creates a unique "flow state" record containing the PKCE code verifier and other session details. This state is stored server-side with a limited lifespan (typically 5-10 minutes). If the user takes too long to complete the OAuth flow, clears browser storage, or if there's a mismatch between the code verifier sent to the provider and the one stored, Supabase cannot retrieve the flow state and returns this error. The error indicates a broken authentication flow rather than invalid credentials, requiring attention to session management, timeout settings, and PKCE implementation consistency.

Typical symptoms

  • OAuth login flow fails after returning from the provider (GitHub, Google, etc.) with HTTP 400 and "flow_state_not_found: PKCE flow state could not be found"
  • Users are redirected back to your app from the OAuth provider but never receive an access token or session
  • The error occurs intermittently, especially for users who take several minutes to complete social login
  • Authentication works in development but fails in production or after browser cache/storage clearing
  • Mobile app authentication fails when switching between browser and app during OAuth flow

Common causes

  1. PKCE flow state expired due to timeout (default 5-10 minutes in Supabase)
  2. Browser session storage (localStorage/sessionStorage) was cleared mid-flow, losing the code verifier
  3. Code verifier mismatch between the authorization request and token exchange
  4. Multiple concurrent OAuth attempts creating conflicting flow states
  5. Supabase Auth service restart or deployment clearing in-memory flow state storage
  6. Incorrect PKCE implementation where code verifier is not properly preserved across redirects
  7. Users opening OAuth flow in one browser tab/window and completing it in another

How to fix "flow_state_not_found: PKCE flow state could not be found"

1Increase PKCE flow timeout in Supabase Auth configuration

By default, Supabase PKCE flow states expire after 5-10 minutes. If users are taking longer (e.g., reading permissions screens), increase the timeout:

javascript
// Server-side configuration (if self-hosting Supabase)
// In your Supabase Auth configuration or environment variables
AUTH_FLOW_STATE_EXPIRY=900  // 15 minutes in seconds

// For managed Supabase, you may need to contact support
// or check project settings for timeout adjustments

If you cannot modify server timeouts, implement client-side warnings when the flow is taking too long.

2Preserve PKCE code verifier in reliable storage

Ensure the PKCE code verifier survives browser redirects and page reloads:

javascript
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(supabaseUrl, supabaseAnonKey)

// Store code verifier in localStorage (survives page reloads)
// instead of sessionStorage (cleared on page reload)
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
  options: {
    // Supabase automatically handles PKCE, but ensure storage is consistent
    redirectTo: 'https://yourapp.com/auth/callback',
    // For custom implementations, store the code verifier:
    // codeVerifier: localStorage.getItem('pkce_code_verifier')
  }
})

// Clean up stored verifier after successful authentication
window.addEventListener('beforeunload', () => {
  localStorage.removeItem('pkce_code_verifier')
})

Consider using IndexedDB for more robust storage if localStorage is being cleared.

3Implement proper PKCE flow recovery and retry logic

Add error handling to detect flow_state_not_found and restart the OAuth flow:

javascript
async function handleOAuthCallback() {
  try {
    const { data, error } = await supabase.auth.getSession()

    if (error?.message.includes('flow_state_not_found')) {
      console.warn('PKCE flow state expired or lost, restarting OAuth flow')

      // Clear any stale auth state
      await supabase.auth.signOut()

      // Restart OAuth flow with fresh state
      const { error: oauthError } = await supabase.auth.signInWithOAuth({
        provider: 'github',
        options: {
          redirectTo: window.location.href,
          skipBrowserRedirect: false
        }
      })

      if (oauthError) throw oauthError
      return
    }

    // Normal session handling...
  } catch (error) {
    console.error('Auth flow error:', error)
    // Show user-friendly message and retry button
  }
}

Provide users with a clear "Try again" button when this error occurs.

4Check for concurrent OAuth flows and race conditions

Prevent multiple simultaneous OAuth attempts that can corrupt flow state:

javascript
let oauthInProgress = false

async function startOAuthFlow(provider) {
  if (oauthInProgress) {
    console.warn('OAuth flow already in progress, waiting...')
    return
  }

  oauthInProgress = true

  try {
    const { error } = await supabase.auth.signInWithOAuth({
      provider,
      options: {
        redirectTo: 'https://yourapp.com/auth/callback'
      }
    })

    if (error) throw error
  } finally {
    // Reset flag after timeout to handle abandoned flows
    setTimeout(() => {
      oauthInProgress = false
    }, 30000) // 30 seconds
  }
}

// Also prevent multiple tabs from initiating OAuth simultaneously
window.addEventListener('storage', (event) => {
  if (event.key === 'oauth_flow_active') {
    if (event.newValue === 'true') {
      console.warn('OAuth flow active in another tab')
      // Show warning or disable login button
    }
  }
})

Store OAuth state in shared storage (localStorage) to detect cross-tab conflicts.

5Verify PKCE implementation for custom OAuth providers

If using custom OAuth providers, ensure proper PKCE implementation:

javascript
// Correct PKCE flow for custom OAuth
import { generatePKCE } from '@supabase/auth-js'

// 1. Generate code verifier and challenge
const { codeVerifier, codeChallenge } = await generatePKCE()

// 2. Store code verifier securely (survives redirects)
localStorage.setItem('pkce_code_verifier', codeVerifier)

// 3. Include code_challenge in authorization request
const authUrl = `https://custom-provider.com/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=https://yourapp.com/auth/callback&response_type=code&code_challenge=${codeChallenge}&code_challenge_method=S256`

// 4. Exchange code for token with stored verifier
async function exchangeCode(code) {
  const codeVerifier = localStorage.getItem('pkce_code_verifier')

  const response = await fetch('https://custom-provider.com/oauth/token', {
    method: 'POST',
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      redirect_uri: 'https://yourapp.com/auth/callback',
      client_id: 'YOUR_CLIENT_ID',
      code_verifier: codeVerifier
    })
  })

  // Clean up after successful exchange
  localStorage.removeItem('pkce_code_verifier')
  return response.json()
}

Ensure code_verifier matches the original code_challenge sent to the provider.

6Monitor and log PKCE flow failures for debugging

Add detailed logging to diagnose flow_state_not_found occurrences:

javascript
// Client-side logging
supabase.auth.onAuthStateChange((event, session) => {
  console.log('Auth event:', event, 'Session:', session)

  if (event === 'SIGNED_IN') {
    console.log('PKCE flow completed successfully')
    // Clear any debugging flags
    localStorage.removeItem('oauth_flow_started')
  }
})

// Log OAuth initiation
localStorage.setItem('oauth_flow_started', Date.now())

// Server-side (if you have access to Supabase logs)
// Monitor these log patterns:
// - "flow_state_not_found" errors in auth logs
// - PKCE flow creation vs completion timestamps
// - User agent and IP patterns for failing flows

// Implement alerting for elevated failure rates
const FLOW_STATE_FAILURE_THRESHOLD = 0.1 // 10%
if (flowStateFailures / totalOAuthAttempts > FLOW_STATE_FAILURE_THRESHOLD) {
  console.error('Elevated PKCE flow state failure rate detected')
  // Send alert to monitoring system
}

Track failure rates by provider, user agent, and time of day to identify patterns.

Advanced notes

### PKCE Flow State Storage in Supabase

Supabase stores PKCE flow states in memory or Redis (depending on configuration) with a default TTL of 5-10 minutes. Each flow state contains:
- Code verifier (the secret)
- Code challenge (hashed verifier sent to OAuth provider)
- Provider configuration
- Redirect URL
- Creation timestamp

When the user returns from the OAuth provider, Supabase looks up the flow state using a combination of provider, code verifier hash, and sometimes a stored session ID. If any component is missing or mismatched, the lookup fails.

### Browser Storage Considerations

Different storage mechanisms have different behaviors:
- sessionStorage: Cleared when tab closes, not shared across tabs
- localStorage: Persists until explicitly cleared, shared across tabs
- IndexedDB: Most robust, survives service worker restarts

For OAuth flows that may involve multiple redirects or app switching (common in mobile), consider using IndexedDB or a combination of localStorage with fallback logic.

### Mobile and Deep Link Considerations

When using Supabase Auth in mobile apps with deep links:
1. The OAuth flow starts in system browser
2. Returns to app via deep link
3. App must preserve PKCE state across this context switch

Implement platform-specific storage that survives the browser-to-app transition, such as secure enclave storage on iOS or encrypted SharedPreferences on Android.

### Scaling Considerations

At high traffic volumes:
- Ensure Supabase Auth service has sufficient memory for flow state storage
- Consider Redis persistence if self-hosting
- Monitor memory usage and flow state eviction rates
- Implement exponential backoff for retries during peak loads

Frequently asked questions

What does "flow_state_not_found: PKCE flow state could not be found" mean?
The "flow_state_not_found" error occurs during Supabase OAuth authentication when the server cannot locate the Proof Key for Code Exchange (PKCE) flow state that was created at the beginning of the authorization process. PKCE is a security mechanism that prevents authorization code interception attacks by binding the authorization request to the token exchange. ), Supabase creates a unique "flow state" record containing the PKCE code verifier and other session details.
How do I fix "flow_state_not_found: PKCE flow state could not be found"?
Supabase returns "flow_state_not_found: PKCE flow state could not be found" when the OAuth authorization flow state is missing, expired, or corrupted. This typically happens when users take too long to complete social login, when browser sessions are cleared mid-flow, or when PKCE code verifiers don't match. The fix involves adjusting flow timeouts, preserving session storage, and ensuring consistent PKCE implementation.
What causes "flow_state_not_found: PKCE flow state could not be found"?
PKCE flow state expired due to timeout (default 5-10 minutes in Supabase) Browser session storage (localStorage/sessionStorage) was cleared mid-flow, losing the code verifier Code verifier mismatch between the authorization request and token exchange
Is the "flow_state_not_found: PKCE flow state could not be found" error serious?
It is a medium-severity error โ€” more of a blocker than a risk. The steps above resolve it.

Official resources & further reading