Fix AuthSessionMissingError: Auth session missing!

Fix AuthSessionMissingError: Auth session missing!

# supabase# supabaseauth# nextjs# troubleshooting
Fix AuthSessionMissingError: Auth session missing!Mahdi BEN RHOUMA

Supabase throws "Auth session missing!" when getUser runs with no session attached. Here is what actually causes it in Next.js and how to fix each case.

AuthSessionMissingError: Auth session missing!
Enter fullscreen mode Exit fullscreen mode

That string comes from @supabase/auth-js, not from the Supabase API. The SDK
raises it locally, before any network request, whenever a method that needs a
session cannot find one. 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 is not consistent about how it surfaces this, and the inconsistency
causes real bugs.

getUser() returns the error:

const { data, error } = await supabase.auth.getUser();
// data.user === null
// error?.name === 'AuthSessionMissingError'
Enter fullscreen mode Exit fullscreen mode

updateUser(), setSession() and refreshSession() throw it.

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
}
Enter fullscreen mode Exit fullscreen mode

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 client persists the session in localStorage and
finds it there on the next call. On the server there is no localStorage 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:

// 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.
          }
        },
      },
    },
  );
}
Enter fullscreen mode Exit fullscreen mode

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:

// 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).*)'],
};
Enter fullscreen mode Exit fullscreen mode

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: reading the session too early on the client

On first paint the browser client has not finished restoring the session from
storage. A getUser() fired at module scope, or in a component body rather than
an effect, can run before the restore completes and report the session as
missing — then a re-render a moment later shows it present.

Subscribe rather than poll:

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();
}, []);
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode
  • 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).

Related failures with different messages

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.


Originally published at https://www.iloveblogs.blog