Skip to content

refactor(website): opaque server-side session instead of JWT-in-cookie#6710

Closed
theosanderson-agent wants to merge 1 commit into
authelia-exp-mergefrom
authelia-session-cookies
Closed

refactor(website): opaque server-side session instead of JWT-in-cookie#6710
theosanderson-agent wants to merge 1 commit into
authelia-exp-mergefrom
authelia-session-cookies

Conversation

@theosanderson-agent

@theosanderson-agent theosanderson-agent commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Stacked on top of #6707 (authelia-exp-merge). Changes the website browser session from "the JWTs are the cookies" to a session-cookie approach, inspired by Stop using JWT for sessions.

Before

authMiddleware stored the OIDC access / id / refresh tokens directly in three httpOnly cookies. The JWTs were the session — sent to the browser on every request, and (because they're stateless) valid until expiry no matter what.

After

  • The browser holds only an opaque, random loculus_session cookie. The tokens live server-side in a session store keyed by that id and never reach the client.
  • Revocable: dropping the server-side entry (logout, or any future ban/force-logout) ends the session immediately instead of waiting for token expiry.
  • Backend calls are unchanged: locals.session.token is still populated per-request (server-side) from the store, so getAccessToken() and every caller keep working untouched.

What changed

  • website/src/middleware/sessionStore.ts (new) — in-process sessionId → tokens store with sliding TTL + periodic sweep. It is correct for the default single-replica website (replicas.website: 1); its limits are documented (lost on restart, not shared across replicas). A horizontally-scaled deployment should swap in a shared store (Redis/Postgres) behind the same create/get/put/delete interface — no other code changes.
  • website/src/middleware/authMiddleware.ts — read/establish/clear the session via the store + the opaque cookie (same runtime-controlled secure/sameSite/httpOnly flags as before). Refreshed tokens are now persisted server-side; legacy access_token/refresh_token/oidc_access_token cookies are cleared on the way through so returning users shed their old JWTs.
  • website/src/pages/logout.astro — destroys the server-side session in addition to clearing the cookie.

Why not Astro's built-in session API?

Astro's session cookie forces secure based on build mode, but Loculus needs it runtime-controlled (!insecureCookies) so the http e2e/dev flow on loculus.test:3000 works. A custom store + the existing cookie machinery keeps that behaviour.

Integration tests

  • The CLI bootstrap previously read the backend token from the access_token cookie, which no longer exists. Added GET /api/cli-bootstrap-token — a dev/CI-only endpoint that returns the current session's access token to that same authenticated session. It is hard-disabled in production (404 unless insecureCookies, which is only true in values_e2e_and_dev.yaml), so the token never leaves the server in a real deployment. CliPage now fetches from it instead of reading the cookie.
  • logout.spec.ts also asserts the loculus_session cookie is cleared.

Local checks

astro check + tsc (0 errors), eslint + prettier, vitest (676 passing), astro build, and integration-tests npm run format.

Known tradeoff

Session durability depends on the store. The in-process default loses sessions on website restart/redeploy (users re-login) and isn't multi-replica-safe — acceptable for the default single replica, flagged for anyone scaling the website. This is the inherent "you need a session store" cost the gist calls out.

🤖 Generated with Claude Code

🚀 Preview: Add preview label to enable

The website previously stored the OIDC access / id / refresh tokens directly
in the browser as httpOnly cookies, i.e. the JWTs were the session. This moves
to a session-cookie approach: the browser holds only an opaque, random session
id and the tokens live server-side in a session store, keyed by that id.

Why (cf. https://gist.github.com/samsch/0d1f3d3b4745d778f78b230cf6061452):
- Revocable: dropping the server-side entry (e.g. on logout) invalidates the
  session immediately, rather than relying on the access token's expiry.
- The JWTs never reach the client at all, shrinking the token's exposure.

Changes:
- New `middleware/sessionStore.ts`: in-process store mapping session id ->
  tokens, with sliding TTL and a periodic sweep. Documented as correct for the
  default single-replica website; a horizontally-scaled deployment should swap
  in a shared store (Redis/Postgres) behind the same interface.
- `authMiddleware.ts`: read/establish/clear the session via the store + an
  opaque `loculus_session` cookie (runtime-controlled `secure` flag, as before).
  Refreshed tokens are now persisted server-side. Legacy token cookies from the
  old scheme are cleared on the way through.
- `logout.astro`: destroy the server-side session as well as the cookie.

Integration tests:
- The CLI bootstrap used to read the backend token out of the `access_token`
  cookie, which no longer exists. Added `GET /api/cli-bootstrap-token`, a
  dev/CI-only endpoint (HARD-DISABLED unless `insecureCookies`, i.e. 404 in
  production) that returns the current session's access token to that same
  session, and pointed the CLI page object at it.
- logout spec now also asserts the `loculus_session` cookie is cleared.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@theosanderson-agent theosanderson-agent added the preview Triggers a deployment to argocd label Jun 17, 2026
@claude claude Bot added the website Tasks related to the web application label Jun 17, 2026
@theosanderson theosanderson removed the preview Triggers a deployment to argocd label Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

website Tasks related to the web application

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants