Tokens and claims
Access, ID and refresh tokens, their claims and lifetimes, scopes and the claims they add, signing keys and algorithms.
#Access tokens
For your API. By default a JWT (RFC 9068):
// header
{ "alg": "RS256", "kid": "k_8f2a…", "typ": "at+jwt" }
// payload
{
"iss": "https://acme.9in.io",
"sub": "usr_8k2…", // the user, or the client ID for client-credentials tokens
"aud": "https://api.acme.com", // the API (the client ID when no audience was requested)
"client_id": "app_7f3k2m9q",
"scope": "openid profile orders:read",
"iat": 1794200000,
"exp": 1794203600,
"jti": "…",
"sid": "…", // the session (absent for client credentials)
"auth_time": 1794199000
}Opaque access tokens (9in_at_… followed by random characters) carry nothing: check them with introspection. Choose the format per tenant (Tokens & Keys), per API, or per application for tokens without an API.
Access tokens don't include profile claims or amr; get those from the ID token or /oauth/userinfo.
#ID tokens
For your app: who signed in and how. A JWT with typ: JWT, always signed with the tenant's default key (asymmetric).
| Claim | |
|---|---|
iss, sub, aud (your client ID), azp, iat, exp | |
auth_time | When the user signed in (unix seconds) |
nonce | From the authorization request |
sid | The session id |
amr | How the user signed in: pwd, otp, social, fed (enterprise), link (login link), and mfa after a second factor |
at_hash | Hash of the access token issued with it (OIDC) |
| Profile claims | Depending on the scopes (below) |
| Custom fields | Fields marked "include as a claim", with the profile scope |
#Refresh tokens
Opaque, issued with offline_access when the application has the refresh token grant (and the API allows refresh tokens). They rotate: every use returns a new refresh token. Using one that was already used revokes the whole family (token.reuse_detected in the activity log). They stop working when their session ends, the user is blocked or deleted, or they're revoked.
#Lifetimes
| Default | Range | Set per | |
|---|---|---|---|
| Access token | 1 hour | 1 minute – 24 hours | Tenant, API, application |
| ID token | 1 hour | 1 minute – 24 hours | Tenant, application |
| Refresh token | 30 days | 5 minutes – 1 year | Tenant, application |
| Authorization code | 60 seconds | 10 seconds – 10 minutes | Tenant |
| Session | 30 days (sliding) | 5 minutes – 1 year | Tenant |
For access tokens, an application's lifetime wins over its API's, which wins over the tenant's.
#Scopes and claims
| Scope | Gives |
|---|---|
openid | An ID token; sub |
profile | name, given_name, middle_name, family_name, nickname, preferred_username, picture, website, gender, locale, zoneinfo, birthdate, and custom-field claims |
email | email, email_verified |
phone | phone_number, phone_number_verified |
offline_access | A refresh token |
| Your API's scopes | Listed in the access token's scope |
Claims with no value are left out.
#Signing keys
Each tenant has a key library (Tokens & Keys):
- Algorithms: RS256, RS384, RS512, PS256, PS384, PS512 (RSA 2048 or 4096 bits), ES256, ES384, EdDSA (Ed25519), and HS256, HS384, HS512 (shared secrets).
- Generate keys, or import your own: a PKCS#8 PEM, a private JWK, or an HMAC secret.
- The default key (asymmetric) signs ID tokens and every access token whose API or application doesn't choose another key. New tenants start with an RS256 key.
- Per API or application: any key from the library, so an API can use ES256 or a shared HMAC secret.
- Rotation: add a key, make it the default, then retire the old one. Retired keys stop signing but stay in the JWKS until you delete them, so tokens signed earlier keep verifying.
- HMAC keys are never published; the secret is shown once when it's created. A token signed with one is only accepted for the API or application that uses that key.
Verifiers must use the algorithm of the key named by kid, not a fixed list: an API's key can be ES256 or EdDSA.