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