Token Lifecycle
Token Types
Keymaster issues three types of tokens:
| Token | Format | Lifetime | Storage |
|---|---|---|---|
| Access Token | RS256 JWT | 15 min (configurable per app) | Client-side or session store |
| Refresh Token | Opaque string (64 hex chars) | 30 days (configurable per app) | Server-side session store only |
| SSO Session | Opaque string (httponly cookie) | 8 hours | km_sso cookie on Keymaster domain |
Access Token (JWT)
Claims
{
"sub": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"name": "Jane Doe",
"aud": "a56e4998-e65d-4817-b69d-009ab7dee28f",
"iss": "https://keymaster.cloud-monitor.com",
"iat": 1710547200,
"exp": 1710548100,
"roles": ["user", "admin"]
}
| Claim | Description |
|---|---|
sub |
User UUID (stable, unique per user across all apps) |
email |
User's email address |
name |
Display name (may be null) |
aud |
App UUID this token was issued for |
iss |
Keymaster base URL |
iat / exp |
Issued-at and expiration timestamps |
roles |
Array of roles for this user in this app |
Verification
Always verify access tokens locally using JWKS:
GET https://keymaster.cloud-monitor.com/.well-known/jwks.json
Check: alg=RS256, iss matches Keymaster URL, aud matches your app_id, exp is in the future.
Do NOT call Keymaster on every request. Cache the JWKS keys (refresh hourly or when kid doesn't match).
Alternative: Server-Side Verification
If you can't verify JWTs locally:
POST /token/verify
{
"token": "eyJhbG...",
"audience": "your-app-id"
}
→ { "valid": true, "claims": { ... } }
→ { "valid": false, "error": "Token has expired" }
Refresh Token Rotation
Refresh tokens are rotated on every use. When you exchange a refresh token, the old one is revoked and a new pair (access + refresh) is issued.
POST /token/refresh
{
"refresh_token": "a1b2c3d4...",
"app_id": "your-app-id"
}
→ {
"access_token": "new-jwt...",
"refresh_token": "new-refresh-token...",
"token_type": "Bearer",
"expires_in": 900
}
Critical: You must store the new refresh token. The old one is now invalid.
Replay Detection
If a revoked refresh token is reused (potential token theft), Keymaster revokes ALL refresh tokens for that user+app combination as a security precaution. This forces the user to re-authenticate.
Correct App Integration Pattern
Store both tokens in a durable session store (database, not memory):
# On each authenticated request:
# 1. Look up session in DB
# 2. Decode access JWT exp claim locally (no network call)
# 3. If expired (or within 30 seconds of expiry):
# → POST /token/refresh → update both tokens in DB
# 4. If refresh returns 401:
# → Token revoked/expired → redirect to login
# 5. If network error:
# → Degrade gracefully (user was previously authenticated)
DO NOT: - Store tokens in memory (lost on server restart) - Use the access token as a one-shot identity check and then ignore it - Forget to update the refresh token after rotation - Set a short cookie expiry that doesn't match the refresh token lifetime
DO:
- Set cookie max_age to 30 days (match refresh token lifetime)
- Refresh proactively (30-second buffer before expiry)
- Handle refresh failure gracefully (redirect to login, don't crash)
Token Revocation
Revoking a Refresh Token (Logout)
POST /token/revoke
{
"refresh_token": "a1b2c3d4..."
}
→ { "status": "ok" }
Always revoke on logout. This prevents the token from being used even if it was intercepted.
Password Change
When a user changes their password, Keymaster automatically revokes all SSO sessions for that user. They must re-authenticate everywhere. Refresh tokens for individual apps are NOT automatically revoked — the app will continue working until the refresh token expires or the user explicitly logs out.
Service Tokens (Client Credentials)
For server-to-server authentication, apps use the client credentials grant:
POST /auth/token
grant_type=client_credentials
client_id={app_id}
client_secret={secret}
scope=push:send
Service tokens are distinct from user tokens:
- token_type: "service" claim (prevents type confusion)
- sub is the app_id, not a user_id
- 15-minute expiry, no refresh — just re-authenticate
- Scoped to specific capabilities (e.g., push:send)
See Server-to-Server Guide for details.
Token Lifetimes at a Glance
| Scenario | What expires | What to do |
|---|---|---|
| Access JWT expires (15 min) | Access token | Call /token/refresh |
| Refresh token expires (30 days) | Refresh token | Redirect to Keymaster login |
| User idle for 30+ days | Both tokens | Redirect to Keymaster login |
| SSO session expires (8 hours) | km_sso cookie |
User sees login screen on next app switch |
| Password changed | All SSO sessions | User re-authenticates everywhere |
| Refresh token replayed | ALL tokens for user+app | User re-authenticates |