Supabase

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.

Mahdi Ben RhoumaMahdi Ben Rhouma
Updated
8 min read
plaintext
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:

js
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:

js
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:

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 middleware file convention is deprecated and renamed proxy.ts (export a function named proxy). middleware.ts still runs, and it stays the option if you need the Edge runtime, which proxy does not support (Next.js 16 upgrade guide). The code below is the same under either name.

js
// 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:

jsx
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:

js
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:

js
const store = await cookies();
console.log('[auth] cookies seen:', store.getAll().map((c) => c.name));
  • No sb-…-auth-token cookie 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

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

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.