How to fix refresh_token_not_found: Refresh token not found in Supabase

SupabaseINTERMEDIATEMEDIUMUpdated Dec 22, 2025· 176 views

This error occurs when Supabase cannot find a valid refresh token during authentication. Refresh tokens are used to obtain new access tokens without requiring users to re-enter credentials. The fix typically involves checking token storage, session management, and authentication flow configuration.

Quick fix

Fixes refresh_token_not_found

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

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)

// Check current session
const { data: { session }, error } = await supabase.auth.getSession()
console.log('Session:', session)
console.log('Error:', error)

// Check if user is authenticated
const { data: { user } } = await supabase.auth.getUser()
console.log('User:', user)
  1. Check current authentication state
  2. Verify token storage configuration
  3. Handle session persistence properly
  4. Fix cross-tab session conflicts
supabase — error
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
// Check current session
const { data: { session }, error } = await supabase.auth.getSession()
console.log('Session:', session)
console.log('Error:', error)
// Check if user is authenticated
const { data: { user } } = await supabase.auth.getUser()
console.log('User:', user)
refresh_token_not_found: Refresh token not found

What this error means

The "refresh_token_not_found" error in Supabase indicates that the authentication system cannot locate a valid refresh token when attempting to refresh an access token. In Supabase's authentication flow: 1. **Access tokens** are short-lived (typically 1 hour) and used for API authorization 2. **Refresh tokens** are long-lived and used to obtain new access tokens without user interaction 3. **Session management** relies on both tokens being properly stored and accessible When a user's access token expires, Supabase automatically attempts to use the refresh token to get a new access token. If the refresh token is missing, invalid, expired, or cannot be retrieved from storage, this error occurs. This issue commonly appears when: - Local storage or cookies are cleared - Multiple tabs/windows cause session conflicts - Authentication state isn't properly persisted - Refresh token rotation is misconfigured - The user logs out from another device/session

Typical symptoms

  • Users get logged out unexpectedly after 1 hour (access token lifetime)
  • Authentication fails with 'refresh_token_not_found' error in console
  • Session doesn't persist across browser restarts
  • Multiple browser tabs show inconsistent authentication states
  • Mobile apps lose authentication after being backgrounded
  • Error appears in Supabase client logs or network requests

Common causes

  1. Local storage/cookies were cleared by browser or user action
  2. Refresh token expired (default 7 days in Supabase)
  3. Multiple tabs/windows creating session conflicts
  4. Incorrect storage method (localStorage vs sessionStorage vs cookies)
  5. Cross-origin issues when app is served from different domains
  6. Supabase client not properly initialized or configured
  7. User logged out from another device, invalidating all refresh tokens
  8. Database row limit reached for auth.sessions table
  9. Refresh token rotation misconfiguration
  10. Third-party cookies blocked by browser privacy settings

How to fix "refresh_token_not_found: Refresh token not found"

1Check current authentication state

First, verify what's happening with the current session:

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

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)

// Check current session
const { data: { session }, error } = await supabase.auth.getSession()
console.log('Session:', session)
console.log('Error:', error)

// Check if user is authenticated
const { data: { user } } = await supabase.auth.getUser()
console.log('User:', user)

This helps identify if the session exists but the refresh token is missing.

2Verify token storage configuration

Ensure you're using the correct storage method for your use case:

javascript
// For web apps that need persistence across browser restarts
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
  auth: {
    storage: localStorage, // Default - persists across browser restarts
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: true
  }
})

// For mobile apps or secure contexts
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
  auth: {
    storage: AsyncStorage, // React Native
    autoRefreshToken: true,
    persistSession: true
  }
})

// For server-side rendering (Next.js, Nuxt)
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
  auth: {
    storage: {
      getItem: (key) => {
        // Implement secure server-side storage
      },
      setItem: (key, value) => {
        // Implement secure server-side storage
      },
      removeItem: (key) => {
        // Implement secure server-side storage
      }
    }
  }
})
3Handle session persistence properly

Implement proper session state management:

javascript
// Subscribe to auth state changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
  (event, session) => {
    console.log('Auth event:', event)
    console.log('Session:', session)

    // Handle specific events
    switch (event) {
      case 'SIGNED_IN':
        // Session created, store tokens
        break
      case 'TOKEN_REFRESHED':
        // Tokens refreshed successfully
        break
      case 'SIGNED_OUT':
        // Clear local storage
        localStorage.removeItem('supabase.auth.token')
        break
      case 'USER_UPDATED':
        // User data updated
        break
    }
  }
)

// Clean up subscription when component unmounts
subscription.unsubscribe()
4Fix cross-tab session conflicts

Handle multiple tabs/windows with broadcast channel:

javascript
// Create a broadcast channel for auth state
const authChannel = new BroadcastChannel('supabase-auth')

// Listen for auth events from other tabs
authChannel.onmessage = (event) => {
  if (event.data.type === 'SIGNED_IN' || event.data.type === 'SIGNED_OUT') {
    // Sync auth state across tabs
    window.location.reload()
  }
}

// Broadcast auth events
const { data: { subscription } } = supabase.auth.onAuthStateChange(
  (event, session) => {
    authChannel.postMessage({ type: event, session })
  }
)

// Or use localStorage events as fallback
window.addEventListener('storage', (event) => {
  if (event.key === 'supabase.auth.token') {
    // Auth state changed in another tab
    window.location.reload()
  }
})
5Implement proper error handling and recovery

Add comprehensive error handling:

javascript
async function handleAuthError(error) {
  console.error('Auth error:', error)

  if (error.message.includes('refresh_token_not_found')) {
    // Refresh token is missing
    console.log('Refresh token not found, attempting recovery...')

    // Option 1: Redirect to login
    // window.location.href = '/login'

    // Option 2: Try to get new session
    const { error: refreshError } = await supabase.auth.refreshSession()

    if (refreshError) {
      // Refresh failed, clear storage and redirect
      await supabase.auth.signOut()
      localStorage.clear()
      window.location.href = '/login?error=session_expired'
    }
  }
}

// Use in your auth calls
try {
  const { data, error } = await supabase.auth.getSession()
  if (error) await handleAuthError(error)
} catch (err) {
  await handleAuthError(err)
}
6Check database and configuration

Verify backend configuration:

1. Check auth.sessions table:

sql
-- Check if sessions exist for user
SELECT * FROM auth.sessions
WHERE user_id = 'user-uuid-here'
ORDER BY created_at DESC
LIMIT 5;

-- Check for session limits
SELECT count(*) as session_count FROM auth.sessions
WHERE user_id = 'user-uuid-here';

2. Verify Supabase project settings:
- Go to Supabase Dashboard → Authentication → Settings
- Check "Session Length" (default: 3600 seconds for access token, 604800 for refresh token)
- Verify "Refresh Token Rotation" is enabled for security
- Check "URL Configuration" matches your app's domain

3. Review RLS policies if using custom session handling

Advanced notes

### Refresh Token Rotation

Supabase supports refresh token rotation for enhanced security. When enabled:
- Each refresh token can only be used once
- New refresh tokens are issued with each rotation
- Compromised tokens become useless after first use

Enable in Supabase Dashboard → Authentication → Settings → Refresh Token Rotation.

### Mobile App Considerations

For React Native/Expo apps:
- Use expo-secure-store or @react-native-async-storage/async-storage
- Handle app state changes (background/foreground)
- Implement deep linking for OAuth callbacks

### Server-Side Rendering (Next.js)

For Next.js App Router:

javascript
// app/layout.tsx
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export default async function RootLayout() {
  const cookieStore = cookies()

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name) {
          return cookieStore.get(name)?.value
        },
        set(name, value, options) {
          cookieStore.set({ name, value, ...options })
        },
        remove(name, options) {
          cookieStore.set({ name, value: '', ...options })
        },
      },
    }
  )

  const { data: { session } } = await supabase.auth.getSession()
}

### Security Best Practices

1. Never store tokens in insecure locations
2. Use HTTP-only cookies for server-side apps
3. Implement proper CORS configuration
4. Regularly rotate refresh tokens
5. Monitor auth.sessions table for anomalies
6. Set appropriate session timeouts based on app sensitivity

### Debugging Tips

1. Check browser DevTools → Application → Storage
2. Monitor Network tab for auth requests
3. Enable Supabase client debugging:

javascript
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
  auth: { debug: true }
})

4. Check Supabase Logs in dashboard for server-side errors

Frequently asked questions

What does "refresh_token_not_found" mean?
The "refresh_token_not_found" error in Supabase indicates that the authentication system cannot locate a valid refresh token when attempting to refresh an access token. In Supabase's authentication flow: 1. Access tokens are short-lived (typically 1 hour) and used for API authorization 2.
How do I fix "refresh_token_not_found"?
This error occurs when Supabase cannot find a valid refresh token during authentication. Refresh tokens are used to obtain new access tokens without requiring users to re-enter credentials. The fix typically involves checking token storage, session management, and authentication flow configuration.
What causes "refresh_token_not_found"?
Local storage/cookies were cleared by browser or user action Refresh token expired (default 7 days in Supabase) Multiple tabs/windows creating session conflicts
Is the "refresh_token_not_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