AuthSessionMissingError: Auth session missing! (Next.js fix)
`AuthSessionMissingError: Auth session missing!` is not a bug report — it is Supabase telling you, accurately, that the client you called had no session to work with. The hard part is that the same message covers a signed-out user, a server client that never received your cookies, a middleware that forgot to forward them, and a session the Auth server has already ended. Each has a different fix.
AuthSessionMissingError: Auth session missing!That string comes from @supabase/auth-js. In most cases the SDK raises it
locally, before any network request, whenever a method that needs a session
cannot find one; it also uses the same error when the Auth server replies with
the session_not_found code. Reading it as "Supabase is broken" sends you looking in
the wrong place; reading it as "this particular client object is empty" sends you
straight to the cause.
There are four ways a client ends up empty, and they need four different fixes.
First: which call produced it?#
The SDK surfaces this as a returned value, not an exception, and that causes real bugs.
getUser() returns the error:
const { data, error } = await supabase.auth.getUser();
// data.user === null
// error?.name === 'AuthSessionMissingError'updateUser(), setSession() and refreshSession() do the same: each catches
the auth error internally and resolves with it in error (see GoTrueClient.ts
in the auth-js source).
They only throw when the client was created with the throwOnError option.
So this code is wrong in a way that will not show up until production:
try {
const { data } = await supabase.auth.getUser();
renderDashboard(data.user); // data.user is null, no exception was thrown
} catch (e) {
redirectToLogin(); // never runs
}Check the error value, or check data.user for null. A try/catch alone will
walk a signed-out visitor straight into your authenticated UI.
Cause 1: a server client built without cookies#
This is the App Router classic, and it accounts for most of the reports.
In the browser, the @supabase/ssr client persists the session in cookies
through document.cookie and finds it there on the next call. On the server
there is no document.cookie and no ambient state — a server-side client only knows what you hand it. If you create
it with the anon key and nothing else, it has no session by construction, and
every getUser() returns Auth session missing! no matter who is signed in.
The server client must be built from the incoming request's cookie store, matching the pattern in Supabase's official Server-Side Auth guide for Next.js:
// utils/supabase/server.js
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export async function createClient() {
const cookieStore = await cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options),
);
} catch {
// Called from a Server Component: middleware handles the refresh.
}
},
},
},
);
}Two details people trip on. cookies() must be awaited in current Next.js
versions — if you have older code destructuring it synchronously, that is a
separate error worth fixing at the same time. And the try/catch around
setAll is deliberate: Server Components cannot write cookies, so the write
throws there and middleware has to do the refreshing instead.
Full wiring, including the client/server split and the route handler cases, is in the complete Supabase session and middleware guide.
Cause 2: no middleware refreshing the session#
Access tokens are short-lived. The browser client refreshes them on its own timer; the server has no timer, so it depends on middleware running on each request to exchange the refresh token and write the new cookies back.
Without it the failure is time-dependent, which is why it reads as flaky: sign
in, everything works, come back an hour later and every server render reports
Auth session missing! while the browser tab still believes it is signed in.
The middleware must both read and write:
Next.js 16: the
middlewarefile convention is deprecated and renamedproxy.ts(export a function namedproxy).middleware.tsstill runs, and it stays the option if you need the Edge runtime, whichproxydoes not support (Next.js 16 upgrade guide). The code below is the same under either name.
// middleware.js
import { createServerClient } from '@supabase/ssr';
import { NextResponse } from 'next/server';
export async function middleware(request) {
let response = NextResponse.next({ request });
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value));
response = NextResponse.next({ request });
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options),
);
},
},
},
);
// This call is what performs the refresh. Do not remove it.
await supabase.auth.getUser();
return response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};Returning a response object that is not the one the cookie writer mutated is
the subtle way to break this: the refreshed cookies get written to an object you
then discard, and the browser never receives them. The symptom is identical to
having no middleware at all.
Cause 3: the session was ended on the server#
A client that held a session a moment ago can lose it too. When the Auth server
answers with the session_not_found error code — the session_id inside the
access token no longer matches a row in the sessions table — auth-js
converts it into AuthSessionMissingError
(lib/fetch.ts).
The comment beside that branch gives the usual reasons: the user signed out, was
deleted, or the session was terminated some other way. getUser() then removes
the stored session itself, which fires a SIGNED_OUT event to any
onAuthStateChange listener.
Calling getUser() early on first paint is not a cause on its own: the method
awaits the client's initialisation (initializePromise) before it reads the
stored session, so it does not race the restore.
Subscribe so the UI follows these changes instead of polling:
useEffect(() => {
supabase.auth.getUser().then(({ data }) => setUser(data.user));
const { data: sub } = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null);
});
return () => sub.subscription.unsubscribe();
}, []);One warning: do not await other Supabase calls inside that callback. It deadlocks the client and every later query hangs forever — the mechanism and the fix are in Supabase hangs after onAuthStateChange.
Cause 4: nobody is signed in#
The dull answer, and often the right one.
For a visitor who never signed in, or who just signed out, Auth session missing! is the correct and expected result. If it is filling your logs, the
problem is that your code treats a normal state as an exception:
const { data, error } = await supabase.auth.getUser();
if (!data.user) {
redirect('/login'); // not an error path — a branch
}
if (error && error.name !== 'AuthSessionMissingError') {
captureException(error); // this one is a genuine failure
}Filter the expected case out of your error reporting and the remaining occurrences become meaningful again.
A quick way to tell the causes apart#
Log one line in the failing server-side code path:
const store = await cookies();
console.log('[auth] cookies seen:', store.getAll().map((c) => c.name));- No
sb-…-auth-tokencookie at all — the browser never sent one. The visitor is signed out (cause 4), or the sign-in redirect never landed on your origin: see the Supabase auth redirect fix. - Cookie present, still missing on the server — the client was built without the cookie store (cause 1).
- Cookie present but stale, and it only fails after a while — middleware is missing or discarding the refreshed response (cause 2).
Once a session exists, the next class of problem is what that session is allowed to read. An authenticated request that returns zero rows is not an auth problem at all — that is row level security filtering silently, covered in why RLS returns zero rows, and you can reproduce your own policy against real Postgres in the RLS Playground.
Two adjacent messages worth not confusing with this one: the
getUser() security warning, which is about
trusting getSession() on the server, and
session persistence failures,
where the session exists but does not survive navigation.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Continue Reading
Supabase Hangs After onAuthStateChange: The Deadlock
Your app loads, the session arrives, and then every Supabase call after it hangs forever. No error, no rejected promise, no network request — the query simply never returns and the loading spinner stays up until someone reloads the page. If you fetch a profile inside your `onAuthStateChange` callback, this is a documented deadlock in supabase-js, and the fix is one line.
Supabase Auth Session Disappears on Refresh? Fix (2026)
Supabase auth sessions mysteriously disappearing after page refresh? Learn the exact cause and fix it in 5 minutes with this tested solution.
Supabase Auth Callback Redirect Not Working? Next.js Fix
Auth redirect not working after Supabase sign-in? Here are the three root causes and the exact fixes — callback route, redirect URL allowlist, and router.refresh().


