Tokens

Understand the ID tokens, access tokens, refresh tokens and codes OneiD issues, their lifetimes, signing keys and how to validate them.

View as Markdown

OneiD issues four kinds of credentials: ID tokens and access tokens, which are signed JWTs, and authorization codes and refresh tokens, which are opaque. This page describes each one, their default lifetimes, the signing keys, and what your application or API must check.

Overview

Credential Format Who reads it Purpose
ID token JWT, signed with RS256 Your application (the client) Tells your application who signed in and how.
Access token JWT, signed with RS256 Your API, or OneiD’s userinfo endpoint Grants access to an API for the scopes it carries.
Refresh token Opaque Only OneiD Gets new tokens without a new sign-in.
Authorization code Opaque Only OneiD Exchanged once for tokens at the token endpoint.

Tokens are signed, not encrypted. Anyone who holds a JWT can read its claims, so keep tokens out of URLs and logs.

ID token

The ID token is a JWT for your application. Its audience (aud) is your client ID. Do not send it to APIs; send the access token instead.

{
  "alg": "RS256",
  "kid": "EXAMPLE_KID"
}

kid names the key in the JWKS that verifies the signature.

Claims

Claim Type When present Description
iss string Always The issuer, https://YOUR_ONEID/, with the trailing slash.
sub string Always The user’s identifier. Opaque and stable. See sub formats.
aud string Always Your client ID.
exp number Always Expiry time, in seconds since the Unix epoch.
iat number Always Issue time, in seconds since the Unix epoch.
nonce string When you sent nonce The nonce from your authorisation request.
auth_time number Always When the user last signed in, in seconds since the Unix epoch.
amr array of strings Always How the user signed in: pwd, mfa, external.
acr string Always The authentication level derived from amr, for example urn:oltin:ac:mfa.
name, given_name, family_name, preferred_username, updated_at, idp string, number for updated_at profile scope granted Profile claims.
email, email_verified string, boolean email scope granted Email claims.
phone_number, phone_number_verified string, boolean phone scope granted Phone claims.
role string or array of strings roles scope granted One value per role.

Claims appear only when the user has a value. The Claims reference lists every claim and the scope that releases it.

ID tokens may also contain private claims whose names start with oi_. Ignore them, and ignore any claim you do not recognise.

Note There is no sid claim in OneiD ID tokens.

Example (decoded payload)

{
  "iss": "https://YOUR_ONEID/",
  "sub": "3f2a9c1e-5b7d-4e1a-9c2b-7d4e8f1a2b3c",
  "aud": "YOUR_CLIENT_ID",
  "exp": 1700001200,
  "iat": 1700000000,
  "nonce": "n-7Hq2Lw81",
  "auth_time": 1699999950,
  "amr": ["pwd", "mfa"],
  "acr": "urn:oltin:ac:mfa",
  "name": "Alex Example",
  "preferred_username": "alex",
  "idp": "local",
  "email": "alex@example.com",
  "email_verified": true,
  "role": "Staff"
}

Access token

OneiD access tokens are signed JWTs (RS256), with a kid header that matches a key in the JWKS. They are not encrypted.

Header

{
  "alg": "RS256",
  "kid": "EXAMPLE_KID"
}

Claims

Claim Type Description
iss string The issuer, https://YOUR_ONEID/.
sub string The user’s identifier. For the client credentials grant, the client ID.
exp number Expiry time, in seconds since the Unix epoch.
iat number Issue time, in seconds since the Unix epoch.
scope string The granted scopes, separated by spaces, for example "openid profile orders.read".
client_id string The client the token was issued to.
jti string A token identifier. May be present.
name string With profile: the user’s name. For the client credentials grant: the client’s display name.
Other profile, email and phone claims various Present when their scope was granted, as in the ID token.
role string or array of strings Present when roles was granted. One value per role.

The access token may contain other claims. Ignore claims you do not recognise.

Warning OneiD access tokens have no aud claim. Do not configure your API to require an audience. Check the issuer, signature, expiry and the scope your API needs instead.

auth_time, amr and acr are in the ID token only, not in the access token. API scopes, such as orders.read, appear only in the scope claim and add no other claims.

Example (decoded payload)

User access token:

{
  "iss": "https://YOUR_ONEID/",
  "sub": "3f2a9c1e-5b7d-4e1a-9c2b-7d4e8f1a2b3c",
  "exp": 1700003600,
  "iat": 1700000000,
  "scope": "openid profile roles orders.read",
  "client_id": "YOUR_CLIENT_ID",
  "name": "Alex Example",
  "preferred_username": "alex",
  "idp": "local",
  "role": ["Staff", "OrdersAdmin"]
}

Client credentials access token:

{
  "iss": "https://YOUR_ONEID/",
  "sub": "YOUR_CLIENT_ID",
  "exp": 1700003600,
  "iat": 1700000000,
  "scope": "orders.read",
  "client_id": "YOUR_CLIENT_ID",
  "name": "Order export service"
}

Refresh token and authorization code

Refresh tokens and authorization codes are opaque, encrypted strings. Never parse them or rely on their length or format. Store them as received.

The authorization code is single-use. Exchange it immediately at the token endpoint.

Refresh tokens behave as follows:

  • Issued when: the client has the refresh_token grant and the authorisation request asked for offline_access.
  • Rotated on every use: each refresh returns a new refresh token. Store the new one and discard the old one.
  • Not sliding: a chain of refreshes ends when the original refresh token lifetime runs out. After that, the user must sign in again.
  • Reuse is detected: a refresh token used a second time after a short grace period (about 30 seconds) is refused with invalid_grant, and the whole chain, including newer refresh tokens, is revoked.
  • The user is checked again: if the user was deleted or locked, or must change their password or enrol in MFA, the refresh fails with invalid_grant.

See Refresh tokens for how to use them safely.

Default lifetimes

Credential Default lifetime Can be changed
Authorization code 5 minutes No, fixed
Access token 1 hour Yes, per client, by an administrator
ID token 20 minutes Yes, per client, by an administrator
Refresh token 14 days Yes, per client, by an administrator

Ask your OneiD administrator if your client uses different lifetimes. The token endpoint’s expires_in tells you the access token lifetime in seconds.

The OneiD browser session is separate from these tokens. It lasts 8 hours and is extended while the user is active. See Sessions, prompt and max_age.

Revocation

You can revoke refresh tokens and access tokens at the revocation endpoint. OneiD also revokes tokens itself when:

  • a user signs out of an application (that application’s tokens for that user),
  • the user’s password changes,
  • an administrator removes a role from the user, locks the user, resets the user’s MFA or sets a temporary password,
  • an administrator disables the client or its client secret.

Revocation takes effect in OneiD’s store. An API that validates access tokens locally does not see the revocation until the token expires. Keep access token lifetimes short. An API that must see revocation immediately can call introspection as a confidential client, but introspection answers only for tokens issued to the calling client, so for most APIs short-lived access tokens with local validation are the recommended pattern.

Signing keys and rotation

  • OneiD signs ID tokens and access tokens with RSA keys (2048-bit by default) using RS256.
  • The public keys are published at https://YOUR_ONEID/.well-known/jwks, each with a kid.
  • When keys are rotated, the new key is published in the JWKS before it starts signing.
  • A retired key stays in the JWKS for 30 days, so tokens it signed still validate.

Cache the JWKS. When a token carries a kid that is not in your cached set, fetch the JWKS again and retry once. Mainstream JWT and OpenID Connect libraries do this for you. Do not hard-code a key.

Validate an ID token

Your application must check every ID token before it trusts it. Most OpenID Connect libraries do this; make sure the checks are switched on.

  1. The token is a JWT signed with RS256. Reject any other algorithm, including none.
  2. The signature verifies with the JWKS key named by kid.
  3. iss equals the issuer from discovery exactly, including the trailing slash.
  4. aud is your client ID.
  5. exp is in the future. Allow at most a small clock skew.
  6. iat is not in the future.
  7. nonce equals the nonce you sent in the authorisation request.
  8. If you sent max_age, auth_time is recent enough.
  9. If your application requires MFA, amr contains mfa (or acr is urn:oltin:ac:mfa). Sending acr_values does not enforce this for you. Note that users from an upstream OpenID Connect provider have amr ["external"]; that provider’s own MFA policy applies.

Validate an access token

Your API must check every access token before it serves the request. See Protect an API for library examples.

  1. The token is a JWT signed with RS256. Reject any other algorithm, including none.
  2. The signature verifies with the JWKS key named by kid.
  3. iss equals the issuer from discovery exactly, including the trailing slash.
  4. exp is in the future. Allow at most a small clock skew.
  5. scope contains the scope the endpoint needs. Split the value on spaces; do not search for a substring.
  6. Do not require an aud claim. OneiD access tokens do not have one.
  7. If your API should serve only certain clients, check client_id against your own list.
  8. Authorise by scope and role. Identify the user by iss and sub, never by email.

Learn more