JWT Lifecycle
The platform uses two-token rotation for browser- and SDK-style clients. Every login (OTP, OAuth, DID-Auth) produces an access_token (short-lived, bearer-signed JWT) and a refresh_token (long-lived opaque or JWT, single-use).
13.5.1 Token claims
Configurable: JWT TTLs are configured in
packages/api/internal/service/auth.go(default 15 min access / 7 days refresh). They are not currently exposed as env vars —JWT_ACCESS_TTL/JWT_REFRESH_TTLdo not exist inpackages/api/internal/config/config.goas of v2.0. Promotion to env-var-driven configuration is planned for v2.1.
13.5.2 Issue flow (OTP example)
The SDK exposes the same flow:
13.5.3 Refresh flow
Behaviour guarantees (verified against service/auth.go):
- Single-use: the old
jtiis removed from the Redis allowlist as soon as the new pair is issued. Re-using the oldrefreshTokenreturns401 INVALID_REFRESH_TOKEN. - Rotation cascade: every refresh produces a new
(accessToken, refreshToken)pair. The expiry clock on the refresh token is reset to a fresh 30 days. - Replay detection: if the same
jtiis presented twice, all sessions for that user are revoked (the user is forced to log in again).
13.5.4 Revocation flow (logout)
Server steps:
- Read access-token claims to find the user.
- Delete the user’s most recent
refresh:jtifrom Redis (or all of them, with?all=1). - Subsequent
POST /auth/refreshreturns401.
Note: access tokens are stateless — they remain valid until their
exp. To force-evict every active session immediately, rotateJWT_SECRET(every existing access token will become unverifiable on next call). Plan this for off-hours; clients will all receive401simultaneously.
13.5.5 Client-side patterns
Auto-refresh middleware (TS SDK / Portal):
Storage hygiene:
Anti-pattern: never put the refresh token in
localStorage— XSS makes it trivially stealable.httpOnlycookies prevent JS access.