Phase 8: Token Model
Document Type: Normative Specification
Phase: 8 - Authentication & Authorization
Status: Active
Overview
This document specifies JWT access token design, claims structure, and token rotation for AeroDB authentication.
Token Types
| Type | Storage | Lifetime | Purpose |
| Access Token | Stateless (JWT) | 15 min | API authentication |
| Refresh Token | Stateful (DB) | 30 days | Access token renewal |
| API Key | Stateful (DB) | Unlimited | Service authentication |
JWT Access Token
Algorithm
- Algorithm: HS256 (HMAC-SHA256)
- Future: RS256 for asymmetric verification
Claims Structure
{
"sub": "user-uuid",
"email": "user@example.com",
"iat": 1707192000,
"exp": 1707192900,
"aud": "aerodb",
"iss": "aerodb-auth",
"role": "authenticated"
}
Standard Claims
| Claim | Type | Description |
sub | string | User ID (UUID) |
email | string | User email |
iat | number | Issued at (Unix timestamp) |
exp | number | Expiration (Unix timestamp) |
aud | string | Audience (always "aerodb") |
iss | string | Issuer (always "aerodb-auth") |
role | string | User role for RLS |
Custom Claims
| Claim | Type | Description |
app_metadata | object | Server-controlled data |
user_metadata | object | User-controlled data |
Token Invariants
- AUTH-T1: Access tokens are stateless (no DB lookup required)
- AUTH-T2: Secrets never appear in JWT payload
- AUTH-T3: JWT signature validated before claims
- AUTH-T4: Expired tokens rejected without exception
- AUTH-T5:
exp claim is mandatory
Token Rotation
Access Token Rotation
Client → Refresh Request → Server
↓
Validate refresh token (DB lookup)
↓
Issue new access token (stateless)
↓
Issue new refresh token (rotate)
↓
Client ← New tokens ← Server
Rotation Rules
- New access token issued on every refresh
- New refresh token issued on every refresh (rotation)
- Old refresh token invalidated immediately
- Refresh reuse triggers session revocation
Token Validation
Validation Order
- Parse JWT structure (reject malformed)
- Validate signature (reject if invalid)
- Check
exp claim (reject if expired) - Check
iss and aud (reject if mismatch) - Extract claims for RLS context
Error Responses
| Condition | HTTP Status | Error |
| Malformed token | 401 | invalid_token |
| Invalid signature | 401 | invalid_token |
| Expired token | 401 | token_expired |
| Wrong issuer/audience | 401 | invalid_token |
Secret Management
JWT Secret
- Generation: Cryptographically random (256-bit minimum)
- Storage: Environment variable or secrets manager
- Rotation: Supported via key versioning (future)
Secret Invariants
- AUTH-K1: Secret never logged or exposed in errors
- AUTH-K2: Secret rotation does not invalidate existing tokens
- AUTH-K3: Minimum secret length: 32 bytes
API Key Tokens
Structure
aero_sk_live_xxxxxxxxxxxxxxxxxxxx
- Prefix:
aero_sk_live_ (live) or aero_sk_test_ (test) - Body: 32 random bytes, base64url encoded
API Key Claims
API keys map to a special context:
RlsContext {
user_id: None,
is_service_role: true, // Bypasses RLS
}