Summary
In production builds on TanStack Start v1, the session cookie set by handleCallbackRoute is missing from the final response. Only the PKCE verifier-delete cookie reaches the browser. Sign-in completes successfully on the server (onSuccess fires with the right user + org), but the next request has no session, so the user gets bounced back to a "not authenticated" state.
Local dev (Vite SSR) is unaffected. The bug is only visible after pnpm build + serving via Node.
Versions
@workos/authkit-tanstack-react-start 0.8.2
@workos/authkit-session 0.5.1 (transitive)
@tanstack/react-start 1.151.x
@tanstack/start-server-core 1.167.x
- Node 24.x
- Deployed via a thin
prod-server.js that wraps the built fetch handler in http.createServer
Pre-0.7 (0.6.0) does not exhibit this. Bug presumably introduced in 0.7.0 with the per-flow PKCE verifier flow (#68) and the ctx.__setPendingHeader / middleware pending-headers architecture. 0.7.x and 0.8.0/0.8.1 not individually tested but share the same code path.
Reproduction
- Multi-org user (3 orgs) signs in via WorkOS hosted picker
- Picks an org → WorkOS redirects to
/api/auth/callback?code=...&state=...
- Callback
onSuccess fires with the correct user and organizationId
- AuthKit redirects to
returnPathname (/setup in our app)
/setup loader calls getAuth() → auth.user is null, no session
- User bounces back to setup/login screen
Diagnostic evidence
We added temporary logging:
Inside onSuccess — confirms WorkOS is returning the right shape:
DEBUG callback onSuccess
userId: user_01KN…
organizationId: org_01KN… (Scryer)
firstName: James
Wrapped handleCallbackRoute(...)(ctx) to log its response before middleware wrap:
DEBUG callback response shape (pre-middleware-wrap)
status: 307
location: https://tisket.com/setup
setCookieCount: 0 ← AuthKit's returned response has zero Set-Cookies
setCookieNames: []
DevTools Network tab on the FINAL response (after authkitMiddleware wraps):
Set-Cookie wos-auth-verifier-a895956b=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax
Only the PKCE verifier-delete cookie is present. No wos-session=....
/setup loader sees:
DEBUG fetchSetupData auth shape (no user)
authKeys: ["user"]
Suspected root cause
In AuthService.handleCallback:
const save = await this.storage.saveSession(response, encryptedSession); // (1) session cookie
let clear = {};
if (cookieName) {
clear = await this.storage.clearCookie(save.response ?? response, cookieName, clearOptions); // (2) verifier delete
}
return {
response: clear.response ?? save.response,
headers: mergeHeaderBags(save.headers, clear.headers),
...
};
Both (1) and (2) flow into TanStackStartCookieSessionStorage.applyHeaders, which on the middleware-context path calls ctx.__setPendingHeader('Set-Cookie', header). The middleware appends each to a pendingHeaders = new Headers() and at the end wraps the response with [...pendingHeaders].
Both calls should land in pendingHeaders as separate Set-Cookie entries. Only the second one reaches the wrapped response.
We could not determine the exact mechanism — candidates we considered but did not confirm:
- Multi-value
Set-Cookie lost during [...pendingHeaders] iteration on certain Node/undici versions in prod (didn't observe in local Vite)
Object.entries(headers) in applyHeaders not iterating something it should (seems unlikely — the input is { 'Set-Cookie': string })
- Some HTTP-layer per-header size cap silently dropping the larger Set-Cookie (the session cookie is several KB encrypted; the verifier delete is small)
The fact that local dev works strongly suggests it's an interaction with the TanStack Start v1 production-build response pipeline rather than AuthKit-only.
Workaround
Pinning back to 0.6.0 (which used the pre-PKCE flow with getSignInUrl() returning a bare URL string and no middleware-pending-cookie dance) restored working sign-in. Trade-off: loses the PKCE/CSRF protection added in 0.7.0 — but that protection is non-functional in 0.8.2 on prod anyway, since the cookie that backs it isn't being delivered.
What would help
- Pointer to whether multi-value Set-Cookie via
ctx.__setPendingHeader is expected to round-trip through the middleware wrap correctly under TanStack Start v1's compiled handler
- Or guidance to fold the session Set-Cookie into the response directly (the way 0.6.0 did) instead of routing it through the middleware pending-header mechanism
Happy to provide more diagnostic data if useful.
Summary
In production builds on TanStack Start v1, the session cookie set by
handleCallbackRouteis missing from the final response. Only the PKCE verifier-delete cookie reaches the browser. Sign-in completes successfully on the server (onSuccessfires with the right user + org), but the next request has no session, so the user gets bounced back to a "not authenticated" state.Local dev (Vite SSR) is unaffected. The bug is only visible after
pnpm build+ serving via Node.Versions
@workos/authkit-tanstack-react-start0.8.2@workos/authkit-session0.5.1 (transitive)@tanstack/react-start1.151.x@tanstack/start-server-core1.167.xprod-server.jsthat wraps the built fetch handler inhttp.createServerPre-0.7 (0.6.0) does not exhibit this. Bug presumably introduced in 0.7.0 with the per-flow PKCE verifier flow (#68) and the
ctx.__setPendingHeader/ middleware pending-headers architecture. 0.7.x and 0.8.0/0.8.1 not individually tested but share the same code path.Reproduction
/api/auth/callback?code=...&state=...onSuccessfires with the correctuserandorganizationIdreturnPathname(/setupin our app)/setuploader callsgetAuth()→auth.userisnull, no sessionDiagnostic evidence
We added temporary logging:
Inside
onSuccess— confirms WorkOS is returning the right shape:Wrapped
handleCallbackRoute(...)(ctx)to log its response before middleware wrap:DevTools Network tab on the FINAL response (after
authkitMiddlewarewraps):Only the PKCE verifier-delete cookie is present. No
wos-session=..../setuploader sees:Suspected root cause
In
AuthService.handleCallback:Both (1) and (2) flow into
TanStackStartCookieSessionStorage.applyHeaders, which on the middleware-context path callsctx.__setPendingHeader('Set-Cookie', header). The middleware appends each to apendingHeaders = new Headers()and at the end wraps the response with[...pendingHeaders].Both calls should land in
pendingHeadersas separate Set-Cookie entries. Only the second one reaches the wrapped response.We could not determine the exact mechanism — candidates we considered but did not confirm:
Set-Cookielost during[...pendingHeaders]iteration on certain Node/undici versions in prod (didn't observe in local Vite)Object.entries(headers)inapplyHeadersnot iterating something it should (seems unlikely — the input is{ 'Set-Cookie': string })The fact that local dev works strongly suggests it's an interaction with the TanStack Start v1 production-build response pipeline rather than AuthKit-only.
Workaround
Pinning back to
0.6.0(which used the pre-PKCE flow withgetSignInUrl()returning a bare URL string and no middleware-pending-cookie dance) restored working sign-in. Trade-off: loses the PKCE/CSRF protection added in 0.7.0 — but that protection is non-functional in 0.8.2 on prod anyway, since the cookie that backs it isn't being delivered.What would help
ctx.__setPendingHeaderis expected to round-trip through the middleware wrap correctly under TanStack Start v1's compiled handlerHappy to provide more diagnostic data if useful.