Access Token Anatomy
What's inside a Xeonr Auth access token, and how to validate one.
Access tokens issued by Xeonr Auth are signed JWTs (JSON Web Tokens). You can decode one at jwt.io for debugging, but always validate the signature in production code.
Header
{
"alg": "RS256",
"kid": "key-id-abc123",
"typ": "JWT"
}| Field | Description |
|---|---|
alg | Always RS256. Reject tokens with any other algorithm. |
kid | Key ID — use this to look up the correct public key from the JWKS endpoint before verifying the signature. |
Claims
A typical user access token:
{
"iss": "https://auth.xeonr.io",
"sub": "urn:xeonr:user:12345",
"aud": ["https://my-api.example.com", "https://auth.xeonr.io"],
"exp": 1711580400,
"nbf": 1711576800,
"iat": 1711576800,
"jti": "550e8400-e29b-41d4-a716-446655440000",
"scope": "openid profile email my-app:read",
"client_id": "660e8400-e29b-41d4-a716-446655440000",
"azp": "660e8400-e29b-41d4-a716-446655440000",
"urn:xeonr:auth:organisation_id": "org-uuid",
"urn:xeonr:auth:application_id": "app-external-id",
"urn:xeonr:auth:authorisation_id": "42"
}Standard claims
| Claim | Description |
|---|---|
iss | Issuer. Always https://auth.xeonr.io. Validate this matches exactly. |
sub | Subject. A stable URN identifying the user (urn:xeonr:user:{id}) or service account (urn:xeonr:serviceaccount:{id}). Use this as your internal user identifier — never use email or username as an ID. |
aud | Audience. An array of strings. Your resource server's identifier must be present in this array — reject the token if it isn't. |
exp | Expiry (Unix timestamp). Reject tokens where exp is in the past. |
nbf | Not Before (Unix timestamp). Reject tokens used before this time. |
iat | Issued At (Unix timestamp). |
jti | Unique token ID. Can be used to detect replay attacks if you maintain a token denylist. |
Application claims
| Claim | Description |
|---|---|
scope | Space-separated list of granted scopes. Check this to authorise access to specific resources or actions. |
client_id | The OAuth client that requested this token. |
azp | Authorized party — the client the token was issued to. Same as client_id for direct grants. |
urn:xeonr:auth:organisation_id | The tenant this token belongs to. |
urn:xeonr:auth:application_id | The external ID of the application the token is scoped to. |
urn:xeonr:auth:authorisation_id | Internal authorization record ID. Useful for correlating with audit logs. |
Optional claims
| Claim | When present | Description |
|---|---|---|
authorization_details | When RAR was used | Array of approved resource-level permissions. See the RAR guide. |
act | Token exchange only | Actor claim (RFC 8693). Contains sub of the client that delegated authority — identifies the original party in a delegation chain. |
sid | When session tracking is active | Session ID, used for OIDC Back-Channel Logout. |
Validating a token
Validate every incoming token. Most JWT libraries handle this for you if configured correctly.
Step 1 — Fetch the JWKS
GET https://auth.xeonr.io/.well-known/jwks.jsonCache the key set and refresh it when you encounter an unknown kid. Do not re-fetch on every request.
Step 2 — Verify the signature
Use the public key matching the token's kid header. Reject the token if no matching key is found, or if the signature doesn't verify.
Step 3 — Validate the claims
Check all of the following:
algisRS256issis exactlyhttps://auth.xeonr.ioaudcontains your resource server's identifierexpis in the futurenbfis in the past (or now)
Step 4 — Check scopes
After signature and claim validation, check that scope contains the permission required for the requested operation.
function hasScope(token: DecodedToken, required: string): boolean {
return token.scope.split(' ').includes(required);
}Service account tokens
Service account tokens follow the same structure, with two differences:
subuses the formaturn:xeonr:serviceaccount:{id}instead ofurn:xeonr:user:{id}scopealways includesservice_account
Check for the service_account scope if your resource server needs to distinguish between user tokens and service account tokens.