Tokens
Understand the ID tokens, access tokens, refresh tokens and codes OneiD issues, their lifetimes, signing keys and how to validate them.
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.
Header
{
"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
sidclaim 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
audclaim. 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 akid. - 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.
- The token is a JWT signed with
RS256. Reject any other algorithm, includingnone. - The signature verifies with the JWKS key named by
kid. issequals theissuerfrom discovery exactly, including the trailing slash.audis your client ID.expis in the future. Allow at most a small clock skew.iatis not in the future.nonceequals thenonceyou sent in the authorisation request.- If you sent
max_age,auth_timeis recent enough. - If your application requires MFA,
amrcontainsmfa(oracrisurn:oltin:ac:mfa). Sendingacr_valuesdoes not enforce this for you. Note that users from an upstream OpenID Connect provider haveamr["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.
- The token is a JWT signed with
RS256. Reject any other algorithm, includingnone. - The signature verifies with the JWKS key named by
kid. issequals theissuerfrom discovery exactly, including the trailing slash.expis in the future. Allow at most a small clock skew.scopecontains the scope the endpoint needs. Split the value on spaces; do not search for a substring.- Do not require an
audclaim. OneiD access tokens do not have one. - If your API should serve only certain clients, check
client_idagainst your own list. - Authorise by
scopeandrole. Identify the user byissandsub, never byemail.