Home

Fix this error · Supabase and Firebase

Fix: AuthSessionMissingError: Auth session missing!

Why supabase.auth.getUser() returns Auth session missing in Next.js, and the fix: @supabase/ssr cookie clients, the session-refresh proxy, getClaims().

supabase-js could not find a session for this client, so auth.getUser() had no access token to send. In Next.js this almost always means the server-side client cannot read the session cookie: it was created with plain createClient instead of @supabase/ssr, the user signed in with a client that stores the session in localStorage, or no proxy (middleware) refreshes the cookie. For a visitor who is simply signed out, the error is expected and should be treated as "no user".

FactDetail
ErrorAuthSessionMissingError: Auth session missing!
Where you see itThe error returned by supabase.auth.getUser(), server logs, the browser console
Usual causeA server client that cannot read the sb-<project_ref>-auth-token cookie
Usual fixCreate clients with @supabase/ssr (createServerClient / createBrowserClient) and add the proxy that refreshes the session
BugTape catches itOnly when your code throws it. getUser() returns the error without throwing

What the error means

AuthSessionMissingError comes from Supabase Auth (auth-js, bundled in supabase-js). Its message is Auth session missing! and its status is 400. getUser() returns it as { data: { user: null }, error } when the client has no access token. Other calls that need a signed-in user, such as updateUser() and refreshSession(), give back the same error when there is no session.

The client finds the session in its storage. In the browser, plain supabase-js uses localStorage. On a server there is no localStorage, so the session has to come from the request cookies. @supabase/ssr provides clients that read and write the session as cookies, named sb-<project_ref>-auth-token by default. If the server client cannot see that cookie, it has no session, and getUser() returns this error even for a signed-in user.

Common causes

  1. The visitor is signed out. Nothing is broken. Code that throws on any getUser() error turns every anonymous visit into an exception.
  2. The server uses createClient from @supabase/supabase-js. It has no cookie storage, so Server Components, Route Handlers and Server Actions never see the session.
  3. The user signed in with a localStorage client. The browser client was plain createClient, so the session never reached a cookie. Use createBrowserClient from @supabase/ssr.
  4. No proxy refreshes the session. Server Components cannot write cookies. Without the proxy, an expired access token is not refreshed and the server stops seeing a valid session. On Next.js 15 and earlier the file must be middleware.ts with export async function middleware. A proxy.ts file is never called there.
  5. A server-side fetch to your own API. A Server Component that calls fetch('/api/…') does not forward the browser's cookies, so the Route Handler sees no session.
  6. The proxy returns a new response without the cookies. Returning a fresh NextResponse drops the refreshed cookies, and the user looks signed out on the next request.

How to fix it

Create the server client with @supabase/ssr

npm install @supabase/ssr @supabase/supabase-js

// lib/supabase/server.ts
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_PUBLISHABLE_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. The proxy refreshes the session.
          }
        },
      },
    },
  );
}

Create a new client in every request. Do not keep it in a module-level variable.

// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr';

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
  );
}

Sign in with this client. The session is then stored in cookies that the server can read.

Add the proxy that refreshes the session

On Next.js 16, the file is proxy.ts. On Next.js 15 and earlier, name it middleware.ts and export middleware instead of proxy. The Supabase code is the same.

// proxy.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';

export async function proxy(request: NextRequest) {
  let supabaseResponse = NextResponse.next({ request });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll();
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value));
          supabaseResponse = NextResponse.next({ request });
          cookiesToSet.forEach(({ name, value, options }) =>
            supabaseResponse.cookies.set(name, value, options),
          );
        },
      },
    },
  );

  // Do not run code between createServerClient and getClaims().
  await supabase.auth.getClaims();

  return supabaseResponse; // return this object, or copy its cookies onto yours
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'],
};

Treat a missing session as signed out

Check for this error by type and show the signed-out state. Report other auth errors.

import { isAuthSessionMissingError } from '@supabase/supabase-js';
import { redirect } from 'next/navigation';
import { createClient } from '@/lib/supabase/server';

export default async function AccountPage() {
  const supabase = await createClient();
  const { data, error } = await supabase.auth.getUser();

  if (error && !isAuthSessionMissingError(error)) throw error; // a real failure
  if (!data.user) redirect('/login');

  return <p>Signed in as {data.user.email}</p>;
}

Supabase now recommends getClaims() to protect pages. It verifies the token's signature and returns data: null with no error when there is no session, so the signed-out case needs no special handling. Never trust getSession() on the server, because it reads the cookie without checking it.

Call Supabase directly instead of fetching your own API

In a Server Component, create the server client and query Supabase there. If you must call a Route Handler from the server, forward the cookie header yourself.

How to catch it in production with BugTape

getUser() returns this error and does not throw it, so BugTape sees nothing unless your code throws or reports it. That is the right default for signed-out visitors. The case worth catching is a signed-in user who suddenly looks signed out, which shows up as redirects to login rather than as an error.

On the server, the Node.js SDK reports errors thrown from pages, Route Handlers, Server Actions and the proxy through Next.js onRequestError, on the Node.js runtime. Proxy runs on Node.js by default in Next.js 16. Middleware on the Edge runtime in older versions is not supported. To report the cases where a session should exist, call captureException with a short context:

// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') (await import('@bugtape/node')).init({ service: 'web' });
}

export async function onRequestError(...args: Parameters<typeof import('@bugtape/node').captureRequestError>) {
  if (process.env.NEXT_RUNTIME === 'nodejs') await (await import('@bugtape/node')).captureRequestError(...args);
}

import * as bugtape from '@bugtape/node';
import { isAuthSessionMissingError } from '@supabase/supabase-js';
import { cookies } from 'next/headers';

const { data, error } = await supabase.auth.getUser();
const hasAuthCookie = (await cookies()).getAll().some((c) => c.name.includes('-auth-token'));

if (error && isAuthSessionMissingError(error) && hasAuthCookie) {
  bugtape.captureException(error, { extra: { where: 'account page' } }); // cookie present, session not read
}

In the browser, an AuthSessionMissingError that your code throws from a click handler or an unhandled promise is reported automatically. The browser SDK has no captureException. Call reportError(error) to send one you handled. It fires the same window error event the SDK listens for.

Repeats group into one issue with a count. Set release so a fix that does not hold reopens the issue as a regression. Your coding agent can read the issue over MCP (npx -y bugtape connect claude, or cursor or codex) and propose the change. You review the pull request.

FAQ

Why does getUser() work in the browser but return Auth session missing on the server?

The browser client found the session in its own storage. The server client only sees cookies. If you signed in with plain createClient, the session is in localStorage and the server never receives it. Switch both clients to @supabase/ssr.

Should I use getUser(), getSession() or getClaims() on the server?

Use getClaims() to protect pages and data. It verifies the token. getUser() also verifies, by calling the Auth server. Do not rely on getSession() on the server, because it reads the cookie without checking it, and anyone can forge a cookie.

Is AuthSessionMissingError a bug?

Not for a signed-out visitor. It means there is no session. Treat it as a null user. It is a bug when a signed-in user gets it, which points at cookies not reaching the server or the session not being refreshed.

Why are users signed out at random?

Usually because the session is refreshed in two places, or the proxy returns a response without the refreshed cookies. Supabase revokes the session when a used refresh token is presented again outside its short reuse window. Refresh only in the proxy and return the response that setAll built.

Sources

All guides: Fix an error.

Start free. Free plan · No credit card · Set up in minutes. Or try the live demo first.