From 55cc75e7d3ce9d8d87179798a5fc71cbbf05737a Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 8 Apr 2026 16:20:18 +0200 Subject: [PATCH] fix(mana-auth): /api/v1/auth/login uses wrong cookie name in production MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The custom /api/v1/auth/login route signs the user in via the better-auth SDK (auth.api.signInEmail) and then forges a request to /api/auth/token to mint a JWT, passing the session token as a synthetic cookie header. The cookie name was hardcoded as `mana.session_token=...`, but in production better-auth issues the session cookie with the __Secure- prefix (because secure: true is enabled). Get-session middleware on the /api/auth/token side couldn't find the session under the unprefixed name, so it returned 401 silently. Result: tokenResponse.ok was false, the route fell through, and the response had no `accessToken` field at all — only the bare { token, user, redirect } from signInEmail. The frontend in @mana/shared-auth then picked this up as `data.accessToken === undefined` and stored undefined as the JWT, while the parallel /api/auth/sign-in/email call masked the visible damage by setting the SSO cookie. So login *appeared* to work in the browser (cookie present, session worked) but the JWT path was always broken. Fix: pick the cookie name based on config.nodeEnv. In production use __Secure-mana.session_token, in development use mana.session_token (no __Secure- prefix because secure: false in dev). Verified end-to-end on auth.mana.how: POST /api/v1/auth/login → response now includes accessToken (a real JWT, EdDSA, with sub/email/role/sid/tier/iss/aud claims), refreshToken (the session token), plus the original signInEmail fields. The other /api/auth/get-session call sites in this file forward the incoming request headers verbatim, so they preserve whatever real cookie the browser sent and don't have this bug. --- services/mana-auth/src/routes/auth.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/services/mana-auth/src/routes/auth.ts b/services/mana-auth/src/routes/auth.ts index 3499d57b2..3fc81f549 100644 --- a/services/mana-auth/src/routes/auth.ts +++ b/services/mana-auth/src/routes/auth.ts @@ -120,13 +120,22 @@ export function createAuthRoutes( } // signInEmail returns { token (session token), user, redirect } - // Use the session token to call Better Auth's JWT /token endpoint + // Use the session token to call Better Auth's JWT /token endpoint. + // + // In production Better Auth issues the session cookie with the + // __Secure- prefix (because secure: true is set), so we have to + // pass that exact cookie name back when forging the request to + // /api/auth/token. Without the prefix the get-session middleware + // can't find the session and the JWT mint silently fails — the + // route falls through and returns a response without accessToken. const sessionToken = response?.token; if (sessionToken) { + const cookieName = + config.nodeEnv === 'production' ? '__Secure-mana.session_token' : 'mana.session_token'; const tokenResponse = await auth.handler( new Request(new URL('/api/auth/token', config.baseUrl), { method: 'GET', - headers: new Headers({ cookie: `mana.session_token=${sessionToken}` }), + headers: new Headers({ cookie: `${cookieName}=${sessionToken}` }), }) );