From 387e10b2fb1bc235116ff9cdca57dcd9ca1e4a96 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 11 May 2026 11:46:01 +0200 Subject: [PATCH] fix(auth/middleware): recognize chunked session cookies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NextAuth v5 chunks the session cookie when the JWT payload exceeds ~4KB (we hit this easily: keycloakId + tenantId + display names + roles + accessToken JWT + idToken). When chunked, the bare 'authjs.session-token' cookie is removed in favour of 'authjs.session-token.0', '.1', etc. Looking up only the bare name returned undefined and the middleware redirected freshly-logged-in users back to /api/auth/signin in a loop. Match presence-only on any cookie whose name starts with either canonical prefix. Server-side NextAuth still validates the token on every RSC render — this check only gates the redirect. Repro: gscCRM/v2.3.3 with the proxy fix in place. Keycloak auth completes, /api/auth/callback/keycloak 302s, but every subsequent request to / 307s straight back to signin because the middleware doesn't see the now-chunked cookie. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/auth/middleware.ts | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/auth/middleware.ts b/src/auth/middleware.ts index 78367da..67d05c2 100644 --- a/src/auth/middleware.ts +++ b/src/auth/middleware.ts @@ -9,6 +9,7 @@ interface NextRequestLike { nextUrl: URL; cookies: { get(name: string): { value: string } | undefined; + getAll(): Array<{ name: string; value: string }>; }; } @@ -62,10 +63,7 @@ export function createAuthMiddleware(opts: AuthMiddlewareOptions = {}) { return NextResponse.next(); } - const sessionCookie = - req.cookies.get("authjs.session-token") ?? - req.cookies.get("__Secure-authjs.session-token"); - if (sessionCookie) { + if (hasSessionCookie(req)) { return NextResponse.next(); } @@ -75,6 +73,32 @@ export function createAuthMiddleware(opts: AuthMiddlewareOptions = {}) { }; } +/** + * NextAuth v5 chunks the session cookie when the JWT payload exceeds + * ~4 KB (claims + accessToken + roles add up fast). When chunked, the + * bare `authjs.session-token` cookie is *removed* in favour of + * `authjs.session-token.0`, `.1`, etc. Looking up only the bare name + * misses the chunked form and the middleware loops the user back to + * signin even though they have a valid session. + * + * Match presence-only on any cookie whose name starts with either + * canonical prefix. Token validity is still verified server-side by + * NextAuth on every RSC render — this check only gates the redirect. + */ +function hasSessionCookie(req: NextRequestLike): boolean { + for (const c of req.cookies.getAll()) { + if ( + c.name === "authjs.session-token" || + c.name === "__Secure-authjs.session-token" || + c.name.startsWith("authjs.session-token.") || + c.name.startsWith("__Secure-authjs.session-token.") + ) { + return true; + } + } + return false; +} + function isAlwaysAllowed(pathname: string): boolean { return ( pathname.startsWith("/api/auth/") ||