Files
twenty/packages/twenty-front/src/modules/auth
Félix Malfait be051c8724 Keep the token pair as a fallback after switching to cookie auth (#23755)
`CookieSessionBootEffect` cleared the token pair the moment it switched
a client onto cookie auth. That leaves the client with a single
credential, and a server that still has
`AUTH_COOKIE_SESSIONS_ENABLED=false` ignores the session cookie entirely
— `extractSessionTokenFromRequest` early-returns when the flag is off. A
cookie-only client is therefore unauthenticated against such a server,
`handleTokenRenewal` finds no refresh token, and
`onUnauthenticatedError` signs the user out.

That is not a hypothetical state. It is every request routed to a
not-yet-rolled pod while the flag is being enabled, and every request
after the flag is rolled back. Requests are load-balanced per request,
so a migrated client hits an old pod almost immediately and gets signed
out; signing back in can migrate it again and repeat for the length of
the rollout.

It also means rollback was not free, contrary to how it was described:
flipping the flag back to `false` signed out everyone who had already
migrated, because the pair they were supposed to fall back to had been
deleted.

## Approach

Keep the token pair as a dormant fallback, and stop *sending* it while
cookie auth is active.

Both halves are needed. Retaining it without suppressing the header
would be worse than the bug: `validateTokenByRequest` checks the Bearer
token first and only falls back to the session cookie when there is
none, so a client that keeps sending Bearer would never exercise the
cookie at all, and `CookieSessionCsrfMiddleware` bypasses on any
Bearer-carrying request. Cookie sessions would silently become a no-op.

So:

- `switchToCookieAuth` no longer nulls the token pair
- the auth link omits `authorization` while cookie auth is active,
leaving the cookie as the credential in use
- on an unauthenticated error while cookie auth is active, the client
deactivates cookie auth once per operation and falls through to the
existing renewal path, which replays with a fresh Bearer

The fallback deliberately goes through renewal rather than replaying
immediately: access tokens live 10 minutes, so the retained one has
usually expired while the client was authenticating by cookie, and an
immediate replay would just fail again.

`isCookieAuthActive` is read and written through `localStorage` from the
link because the links run per request and must agree with the atom
synchronously — a React state update lands a render too late to affect
the request being built.

## Follow-up

This trades the immediate removal of the token pair from `localStorage`
for rollout safety, so the XSS-exfiltration surface that cookie sessions
close stays open a while longer. Once cookie sessions are stable across
every environment, the retained pair should be dropped — reverting to a
clear on `switchToCookieAuth` is a one-line change.

## Test

Three cases added to `apollo.factory.test.ts`: no Bearer header while
cookie auth is active; an unauthenticated response falls back and
replays with the token pair rather than calling
`onUnauthenticatedError`; and the fallback is attempted only once before
going through renewal. The existing `CookieSessionBootEffect` assertion
that the pair is cleared is inverted to assert it is retained.


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23755?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-08-04 17:12:51 +02:00
..