Skip to main content

JWT and HttpOnly cookies

Authentication tokens are placed by the backend in HttpOnly cookies, which JavaScript cannot read. The frontend no longer stores them in localStorage: an XSS flaw cannot steal them.

Lifetimes, routes and key management: see JWT authentication.

Cookies set by the backend​

CookieContentPathLifetimeAttributes
BEARERJWT access token/1 hourHttpOnly; Secure and SameSite=Strict over HTTPS, Lax over local HTTP
REFRESH_TOKENrefresh token/api/token30 dayssame
XSRF-TOKENanti-CSRF token/1 hour, renewedreadable by JavaScript, by design; same Secure and SameSite

They are set by JWTCookieSubscriber on sign-in, 2FA verification, magic link and refresh responses, and cleared on POST /api/token/invalidate. The sign-in response body still contains the token, for command-line clients.

Token reading order​

  1. BEARER cookie (browser).
  2. Authorization: Bearer … header (scripts, tools, integrations).

The refresh token is read the same way: REFRESH_TOKEN cookie, otherwise the refresh_token field of the body.

CSRF protection​

A cookie is sent automatically with every request to the domain. So that a third-party site cannot trigger actions on the user's behalf, mutations (POST, PUT, PATCH, DELETE) authenticated by cookie must carry the X-CSRF-TOKEN header equal to the XSRF-TOKEN cookie (CsrfProtectionSubscriber). The frontend Axios client adds it by itself.

Exempt: sign-in, 2FA verification, refresh, sign-out, forgotten password, magic link, health and public routes (/api/public/, /api/cms/public/). Calls authenticated by the Authorization header are not concerned.

The check is on by default; CSRF_ENABLED=false turns it off, in development only.

Frontend side​

The Axios client (frontend/src/services/api/client.ts) sends cookies (withCredentials: true) and keeps the access token in memory only. On page reload, it calls /api/token/refresh before the first request: the REFRESH_TOKEN cookie is enough to restore the session.

Check​

In the browser developer tools, Application → Cookies tab: BEARER and REFRESH_TOKEN must appear with the HttpOnly box ticked, and Secure on the servers.

curl -si -X POST https://sig-preprod.ucp-pch.org/api/login \
-H "Content-Type: application/json" \
-d '{"email":"<EMAIL>","password":"<PASSWORD>"}' | grep -i "^set-cookie"