# ADR-0017: JWT Token Strategy ## Status Accepted ## Context The platform needs authentication tokens that: - Are stateless (no server-side session storage) - Support role and permission claims - Can be revoked (challenge) - Have appropriate lifetimes - Support multi-tenancy (tenant ID in claims) Token strategies considered: 1. **Short-lived access tokens + long-lived refresh tokens** - Industry standard 2. **Single long-lived tokens** - Simple but insecure 3. **Short-lived tokens only** - Secure but poor UX 4. **Session-based** - Stateful, requires storage ## Decision Use **short-lived access tokens + long-lived refresh tokens**: 1. **Access tokens**: 15 minutes lifetime, contain user ID, roles, tenant ID 2. **Refresh tokens**: 7 days lifetime, stored in database (for revocation) 3. **Token format**: JWT with claims: `sub` (user ID), `roles`, `tenant_id`, `exp` 4. **Revocation**: Refresh tokens stored in DB, can be revoked/deleted **Rationale:** - Industry best practice (OAuth2/OIDC pattern) - Good balance of security and UX - Access tokens can't be revoked (short lifetime mitigates risk) - Refresh tokens can be revoked (stored in DB) - Supports stateless authentication for most requests ## Consequences ### Positive - Secure (short access token lifetime) - Good UX (refresh tokens prevent frequent re-login) - Stateless for most requests (access tokens) - Supports revocation (refresh tokens) ### Negative - Requires refresh token storage (DB table) - More complex than single token - Need to handle token refresh flow ### Implementation Notes - Use `github.com/golang-jwt/jwt/v5` for JWT handling - Store refresh tokens in `refresh_tokens` table (user_id, token_hash, expires_at) - Generate access tokens with HS256 or RS256 signing - Include roles in token claims (not just role IDs) - Validate token signature and expiration on each request - Refresh endpoint validates refresh token and issues new access token