← All posts
·6 min read·GuardLayer

Fix: Failed to parse cookie string base64-eyJ

SupabaseAuthNext.jsDependencies

Failed to parse cookie string: SyntaxError: Unexpected token 'b', "base64-eyJ"... is not valid JSON means the deprecated @supabase/auth-helpers package is trying to JSON.parse() a session cookie that the newer Supabase auth stack (@supabase/ssr + auth-js) writes base64-encoded with a base64- prefix. The fix is to migrate to @supabase/ssr — auth-helpers is unmaintained and will never learn the new format.

The base64-eyJ fragment in the message is the giveaway. eyJ is the base64 opening of any JSON Web Token, so what you're looking at is a JWT-bearing cookie with a marker prefix in front of it, handed to a parser that expects raw JSON.

What's actually happening

Somewhere in the 2.x line, the modern Supabase auth stack changed how the session cookie is serialised: instead of storing JSON directly, the payload is base64-encoded and prefixed with the literal string base64-. The prefix is a version marker, so a reader knows which decoding path to take.

@supabase/ssr knows about it. @supabase/auth-helpers does not. Auth-helpers reaches straight for JSON.parse(), hits the b of base64-, and throws a SyntaxError that it catches and logs.

The two packages are typically dragged into the same project transitively — you install @supabase/auth-helpers-nextjs, it pulls @supabase/supabase-js, npm resolves ^2.x to whatever's current, and the current one writes cookies the older helper can't read. Nothing in your code changed. A lockfile update was enough.

The discussion thread reports it against @supabase/auth-helpers 0.7.x with @supabase/auth-js 2.65.1+, and the resolution there is the same one Supabase gives everywhere: move to @supabase/ssr.

Is this error breaking my app or just noise?

Usually noise first, breakage later. The parse failure is caught and logged, so the immediate visible effect is console spam. But every log line is a session read that returned nothing. Where auth-helpers falls back to "no session", the app treats a signed-in user as anonymous — so the symptoms escalate from noisy logs to intermittent redirects to /login, server renders showing logged-out UI, and RLS queries returning zero rows because no user JWT reached Postgres.

That last one presents identically to a broken policy. Before you go rewriting RLS, confirm auth.uid() is actually populated — a NULL auth.uid() fails every policy at once, and a cookie your auth layer can't parse is one way to get there.

How do I fix the base64 cookie parse error?

Migrate to @supabase/ssr. There is no patch coming for auth-helpers — it's deprecated, so the fix is the migration.

npm uninstall @supabase/auth-helpers-nextjs @supabase/auth-helpers-react
npm install @supabase/ssr @supabase/supabase-js

Then replace the client factories. Browser side:

// utils/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr";

export const createClient = () =>
  createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );

Server side — note this helper is async, because cookies() returns a Promise in Next.js 15+:

// utils/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_ANON_KEY!,
    {
      cookies: {
        getAll: () => cookieStore.getAll(),
        setAll: (cookiesToSet) => {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            );
          } catch {
            // Server Component — middleware handles the refresh.
          }
        },
      },
    }
  );
}

The import swaps map cleanly:

auth-helpers@supabase/ssr
createClientComponentClient()createBrowserClient(url, key)
createServerComponentClient({ cookies })createServerClient(url, key, { cookies })
createRouteHandlerClient({ cookies })createServerClient(url, key, { cookies })
createMiddlewareClient({ req, res })createServerClient(url, key, { cookies }) wired to request/response

Then clear the old cookies once. Users carrying a cookie written by the old format can stay stuck after the upgrade. In dev, clear site data. In production, the middleware refresh rewrites them on the next request — but if you see stale chunk cookies shadowing the live session, that's a separate bug with its own fix.

The fix that trades a warning for an unpatched auth layer

The tempting alternative is to make the error go away without a migration: pin @supabase/supabase-js back to a version that writes the old cookie format, or hand-roll a cookie reader that strips the base64- prefix before parsing.

The pin works. The hand-rolled reader works only until a session grows past @supabase/ssr's chunk threshold and gets split across numbered cookies, at which point a naive strip-and-parse breaks again. Both leave you here:

{
  "name": "my-app",
  "dependencies": {
    "@supabase/auth-helpers-nextjs": "^0.10.0",
    "@supabase/supabase-js": "^2.65.1",
    "next": "16.2.11",
    "react": "19.2.0"
  }
}
guardlayer scan · package.jsonLive engine output
Passed with warnings
90/100 · A
  • Warningpackage.json:6

    Dependency with a known advisory

    Upgrade to the patched version and run npm audit to confirm the advisory is resolved.
  • Infopackage.json:4

    Deprecated, unmaintained dependency

    Migrate to the maintained replacement. For @supabase/auth-helpers, move to @supabase/ssr — see Supabase's auth-helpers → SSR migration guide.

Pinning the client library is the worse half of the deal. You're now holding an old @supabase/auth-js — the code that handles your tokens, refresh flow, and PKCE exchange — pinned specifically to avoid upgrading, on top of an auth layer that receives no fixes at all. When an advisory lands against either, there is no patched release to move to on the auth-helpers side, and your pin blocks the one on the client side.

That's why GuardLayer flags @supabase/auth-helpers-* as a finding in its own right rather than waiting for a CVE: the risk isn't a known vulnerability today, it's that the package has no maintainer to publish a fix tomorrow. Same reasoning applies to any pinned, known-vulnerable dependency — an unmaintained auth layer is the one place you least want to be stuck.

Quick self-check

# Still on the deprecated package, directly or transitively?
npm ls @supabase/auth-helpers-nextjs @supabase/auth-helpers-react

# Any auth-helpers imports left after the migration?
grep -rn "@supabase/auth-helpers" --include=*.ts --include=*.tsx .

# Old client factories still in use?
grep -rn "createClientComponentClient\|createServerComponentClient\|createRouteHandlerClient\|createMiddlewareClient" --include=*.ts --include=*.tsx .

All three clean and the parse error is gone permanently, not just quiet until the next lockfile bump.

FAQ

What does "Unexpected token 'b', base64-eyJ is not valid JSON" mean? Something called JSON.parse() on a Supabase session cookie that is base64-encoded with a base64- prefix. It's almost always the deprecated @supabase/auth-helpers reading a cookie written by a newer @supabase/auth-js.

Can I ignore it? Not for long. Each occurrence is a failed session read, which surfaces as spurious logouts, logged-out server renders, and RLS queries returning nothing.

Is @supabase/auth-helpers really deprecated? Yes. Supabase moved server-side auth to @supabase/ssr and no longer ships fixes to auth-helpers. Anything still importing it is running an unmaintained auth layer.

Can I just downgrade supabase-js to make it stop? It works and it's the wrong trade. You'd be pinning the library that handles your tokens and refresh flow to an old version, on top of a package that can't be patched at all.

Do users need to log in again after migrating? Usually not — middleware rewrites the cookie on the next request. Clear site data in local development if a stale cookie keeps you stuck.

Why did this start without any code change on my side? A transitive ^2.x resolution bumped @supabase/auth-js to a version that writes the new cookie format. Your lockfile changed; your code didn't.

Catch this before it ships — free

GuardLayer scans every push for this and 28 other Next.js + Supabase issues, with the exact fix inline.

No signup, no card — your code is scanned in memory and never stored.

Keep reading

A Solvion project — see also Reglog — EU AI Act changelog, Proceedly, Solenna and Solvion Solutions.