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

40 lines
1.7 KiB
Markdown

# 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.