Files
web_sport/docs/adr/0008-short-access-tokens-rotating-refresh-tokens-separate-audiences.md
T

1.7 KiB

ADR-0008: Short access tokens, rotating refresh tokens, separate audiences

  • Status: Accepted
  • Date: 2026-08-11

Context

Storefront customers and back-office staff authenticate against the same API. A single token type shared between them means an XSS on the storefront is a path into the admin.

Decision

A short-lived (15 min) stateless JWT access token carrying the permission set, plus a long-lived refresh token that is opaque to the client, delivered as an httpOnly SameSite cookie, stored server-side only as a SHA-256 hash, and rotated on every use.

Rotation is tracked with Session.replacedById. Presenting an already-rotated refresh token means the token leaked, so the entire token family is revoked — theft detection, not just theft mitigation.

Every token carries an audience (storefront or admin). Admin controllers declare @RequireAudience('admin'), and the check runs before any permission logic.

Consequences

A stolen access token expires in minutes. A stolen refresh token is detectable and self-revoking. A stolen storefront token is rejected by admin endpoints on audience alone, before permissions are consulted.

The trade-off is that permission changes are not instantaneous — bounded by the access token lifetime. For an immediate kill switch, CACHE_KEYS.revokedSession exists as a Redis denylist checked per request; it is deliberately not enabled by default because it reintroduces a hot-path lookup.

Alternatives considered

Server-side sessions — simpler to revoke, but adds a datastore read to every request and complicates a future mobile client. Long-lived access tokens — rejected: the blast radius of a leak is unacceptable for a system holding payment and address data.