The "session_not_found" error occurs when Supabase Auth detects that the session ID in your JWT token no longer exists in the auth.sessions table. This typically happens due to session expiration, token refresh failures, or URL mismatches between your Supabase configuration and frontend application.
When you use Supabase authentication, the system creates a session and stores it in the auth.sessions table with a unique session ID. The JWT token that your client receives contains a claim referencing this session ID. The "session_not_found" error is returned when you attempt to use an access token, but the session ID embedded in that token is no longer valid or has been deleted from the database. This error specifically means: "Session from session_id claim in JWT does not exist." It can occur on the client side (when calling auth.getSession()) or on the server side (when calling auth.getUser() in API routes or middleware). The error can leave users unable to perform authenticated actions or even log themselves out.
Update your Supabase JavaScript and SSR packages to the latest versions. Several fixes have been released to handle session_not_found errors:
npm install @supabase/supabase-js@latest
npm install @supabase/ssr@latestCheck your package.json to ensure you're running:
- @supabase/supabase-js v2.39.0 or later
- @supabase/ssr v0.0.10 or later
Older versions had session refresh bugs that could leave you in a stuck state.
A common cause is setting the Supabase site URL to 127.0.0.1 but accessing your frontend via localhost (or vice versa). These are treated as different origins and cause session validation to fail.
1. Go to your Supabase dashboard
2. Navigate to Project Settings > API
3. Check "Site URL" - it should match exactly how you access your frontend
For local development, use consistent URLs:
# Use localhost consistently:
Site URL: http://localhost:3000
# OR use 127.0.0.1 consistently:
Site URL: http://127.0.0.1:30004. Restart your development server after changing the site URL
5. Clear browser cookies for localhost and 127.0.0.1 to force fresh login
On the server side (API routes, middleware), always use auth.getUser() instead of auth.getSession(). The getUser() method makes a request to verify the session server-side, while getSession() just reads local data that could be tampered with.
For Next.js with SSR:
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export async function GET(request: Request) {
const cookieStore = await cookies();
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll();
},
},
}
);
const { data: { user }, error } = await supabase.auth.getUser();
if (error || !user) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
return new Response(JSON.stringify({ user }));
}getUser() always makes a fresh request to verify the session exists, preventing stale session errors.
Wrap authentication calls in error handling that gracefully catches session_not_found and forces a logout:
async function callAuthenticatedEndpoint() {
try {
const { data: { user }, error } = await supabase.auth.getUser();
if (error?.message.includes('session_not_found')) {
console.warn('Session expired, signing out...');
await supabase.auth.signOut();
window.location.href = '/login';
return;
}
if (error) throw error;
return user;
} catch (error) {
console.error('Auth error:', error);
// Force sign out on any auth error
try {
await supabase.auth.signOut();
} catch (signoutError) {
// Ignore errors during logout - just clear locally
localStorage.removeItem('supabase.auth.token');
}
window.location.href = '/login';
}
}This prevents users from getting stuck in a failed state - they're automatically logged out and redirected to login.
Short JWT expiration times can cause clock skew issues where the token expires before the refresh mechanism can kick in. Increase the expiration time in your Supabase dashboard:
1. Go to Supabase Dashboard > Project Settings > Auth
2. Scroll to JWT Settings
3. Set "JWT expiry limit" (in seconds):
- Minimum: 3600 (1 hour)
- Recommended: 86400 (24 hours) or more for better UX
- Access tokens: Leave at 3600-7200 (1-2 hours)
Access token (JWT) expiry limit: 3600 seconds (1 hour)
Refresh token expiry limit: 604800 seconds (7 days)Supabase client libraries automatically attempt to refresh sessions before expiry, so longer expiration times give more buffer. The refresh token can be long-lived since it's used server-to-server.
If you're experiencing session_not_found during password recovery specifically, there's a known issue with the standard @supabase/ssr that's fixed in the patched version:
npm install @supabase/ssr@patchedThis version fixes issues where the session gets invalidated during the password reset callback flow. After updating, test password recovery end-to-end to ensure the session persists correctly.
Instead of manually checking sessions, let Supabase automatically manage session state with the onAuthStateChange listener:
import { useEffect, useState } from 'react';
export function useCurrentUser() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Listen for auth state changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
if (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED') {
setUser(session?.user ?? null);
} else if (event === 'SIGNED_OUT' || event === 'USER_DELETED') {
setUser(null);
}
setLoading(false);
}
);
return () => {
subscription.unsubscribe();
};
}, []);
return { user, loading };
}This automatically handles session refreshes and invalidations, reducing the chance of stale session errors.
The session_not_found error was introduced as a security feature to better detect when JWT tokens contain references to sessions that have been revoked or expired. Previously, Supabase was returning less specific errors that could mask security issues.
Session Recovery Timing: The Supabase client library's _recoverSession() method is asynchronous. By the time you call any supabase.auth methods, the session might not have recovered yet. If you're calling auth methods immediately on app load, introduce a small delay or use onAuthStateChange() which waits for session recovery automatically.
Self-hosted Supabase: If you're running self-hosted Supabase, ensure your JWT secret is properly configured with at least 32 alphanumeric characters. Malformed secrets can cause session validation failures.
Rate Limiting: The session_not_found error is sometimes caused by rate limiting on the Supabase Auth service. If you're experiencing this error sporadically on high-traffic periods, check your project's rate limits and upgrade if needed.
Tab Synchronization: In applications with multiple browser tabs, one tab's logout can cause other tabs to encounter session_not_found since they share the same session. Implement cross-tab communication with the Broadcast Channel API to handle this gracefully.
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