# Tokens

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

Source: https://oltinid.com/docs/reference/tokens/ · Section: Reference · All OneiD documentation: https://oltinid.com/llms.txt

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

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

`kid` names the key in the [JWKS](https://oltinid.com/docs/reference/endpoints/#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](https://oltinid.com/docs/reference/claims/#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](https://oltinid.com/docs/reference/claims/) 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)

```json
{
  "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

```json
{
  "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:

```json
{
  "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:

```json
{
  "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](https://oltinid.com/docs/guides/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](https://oltinid.com/docs/guides/sessions-and-reauthentication/).

## Revocation

You can revoke refresh tokens and access tokens at the [revocation endpoint](https://oltinid.com/docs/reference/endpoints/#revocation). 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](https://oltinid.com/docs/reference/endpoints/#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](https://oltinid.com/docs/guides/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

- [Claims](https://oltinid.com/docs/reference/claims/)
- [Protect an API](https://oltinid.com/docs/guides/protect-an-api/)
- [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/)
