Most token-based authorisation bugs come from treating every value in a JWT as a permission, when a scope records what a client was allowed to do on someone's behalf and most other claims only describe who someone is. Both sit in the same token and look alike when decoded, so teams drift into authorising on an email domain, a directory group name or a role stuffed into an ID token. This guide compares claims and scopes as authorisation inputs: what the standards say each one means, the anti-patterns that turn claims into a privilege escalation path, how roles and permissions belong in access tokens, and how delegation tokens for AI agents should carry the actor as well as the user. It is for API owners, IAM architects and agent builders. For scope naming and granularity see the OAuth Scope Design Guide; this page does not repeat it.
Key takeaways
- A scope is a consented ceiling, not an entitlement. It says what the client may ask the API to do for this subject; the API still has to check what the subject may do to this object.
- Identity claims identify, they do not authorise. OpenID Connect says only
issplussubis a stable identifier;email,nameandpreferred_usernamemust never be keys for access decisions. - Roles and permissions belong in access tokens for one audience. RFC 9068 gives
roles,groupsandentitlementsclaims, but only an access token addressed to your API, never an ID token, should drive them. - A claim is only as trustworthy as the key and the issuer behind it. Validate
typ,iss,aud, signature and expiry before reading any authorisation claim, and protect signing keys as tier zero assets. - Agent tokens need two identities. Use token exchange so the token names the user as
suband the agent asact, with narrowed scopes and audience, and decide on the current actor, not the original caller alone.
The short answer
Use scopes to express the delegated grant: which operations a specific client may perform at a specific API for the subject who consented, or for itself in the client credentials case. Use claims to carry the facts the API needs to make its own decision: the subject identifier, the tenant, the client, the actor, and, where the authorisation server is the source of truth, assigned roles or entitlements. The API then combines both with its own data: scope permits the operation class, role or entitlement permits the function, and an object-level check against the resource confirms this subject may touch this record. Never let a single claim, and never let the scope alone, decide access to an object.
Token anatomy: what each part means
An access token issued under RFC 9068, the JWT profile for OAuth 2.0 access tokens, must carry seven claims: iss, exp, aud, sub, client_id, iat and jti, with a typ header of at+jwt. When the request included a scope parameter the token should include a scope claim, and each scope string must have meaning for the resources named in aud. RFC 6749 defines scope values as space-delimited, case-sensitive strings whose meaning the authorisation server defines, and it lets the server grant less than requested as long as it returns the scope it actually issued.
| Element | Kind | What it tells the API | Safe to authorise on? |
|---|---|---|---|
iss | Registered claim | Which authorisation server issued the token | Only as a precondition: exact match to the expected issuer |
aud | Registered claim | Which API the token is for | Precondition: reject if your resource identifier is absent |
sub | Registered claim | The resource owner, or the client itself in client credentials | Yes, as the key for object ownership checks, scoped by iss |
client_id | Registered claim | The application that holds the token | Yes, for client allowlists and per-client limits |
scope | Grant | The delegated operations consented for this client at this API | Yes, as a ceiling on operation classes |
roles, groups, entitlements | Authorisation claims (RFC 9068 section 2.2.3.1) | The subject's assigned privileges, drawn from SCIM User attributes | Yes, if issued by your trusted server for your audience |
authorization_details | Grant (RFC 9396) | A structured, fine-grained grant: type, locations, actions, datatypes, identifier, privileges | Yes, compared with each request's parameters |
act, may_act | Delegation claims (RFC 8693) | The current actor and who may become an actor | Yes, the current actor is part of the decision |
email, name, preferred_username | Identity claims (OIDC) | Display and contact data | No: mutable and, per OIDC section 5.7, not unique identifiers |
Two points in the standards are often missed. First, RFC 9068 section 4 lists the checks a resource server must make (typ, issuer, audience, signature, expiry) but leaves authorisation itself to the API: if authorisation claims are present it should use them in combination with other context. Second, RFC 9068 section 6 says the client must not inspect the access token at all; it is addressed to the API, and that applies equally to a front end reading roles and to an agent parsing its own token.
ID tokens are not access tokens
OpenID Connect Core requires an ID token to list the relying party's client_id as an audience. It tells the client that a user authenticated (auth_time, nonce, acr, amr); the profile, email, address and phone scopes only request user claims. Sending an ID token to an API as a bearer credential means the API accepts a token issued for someone else and skips the consent recorded in scope. See the OAuth 2.0 vs OIDC guide for the protocol split.
Claims and scopes side by side
| Question | Scopes | Claims |
|---|---|---|
| What it represents | A grant: permission delegated to a client | An assertion: a fact about the subject, client or actor |
| Who decides the value | Client requests, user or admin consents, server narrows | The issuer, from its directory, policy or upstream identity provider |
| Who it binds | The client application, within the subject's own rights | The subject, regardless of which client presents it |
| Typical granularity | Operation class at one API (invoices:read) | Attribute, membership or role (roles: ["approver"]) |
| Changes when | Consent is granted, reduced or revoked | The directory or HR record changes, or the user re-authenticates |
| Staleness risk | Long-lived refresh tokens keep old consent alive | Role removed in the directory stays in tokens until expiry |
| Main failure | Over-broad scope used as the only check (missing object checks) | Untrusted or mutable claim used as a permission |
| Where enforced | Resource server or gateway, per endpoint | Resource server, combined with object data and policy |
A scope limits the client; a claim describes the subject. An administrator using a calendar widget granted only calendar.read must not delete users through it, and a client granted users.delete must not delete users for someone whose role does not allow it. Effective permission is the intersection.
How three identity platforms express the split
Platforms are listed alphabetically; the order is not a ranking.
- Auth0: with Enable RBAC turned on for an API, the
scopeclaim contains only the intersection of the permissions the application requested and the permissions assigned to the user. Add Permissions in the Access Token adds apermissionsclaim listing all of the user's permissions for that API. Actions that modify access token scopes override the RBAC result, so treat any such Action as authorisation code and review it. - Microsoft Entra ID: user (delegated) access tokens carry
scp, the consented scopes, and the API is told to verify these against the scopes it exposes before deciding. Application tokens from the client credentials flow carryrolesinstead; in user tokensrolesholds the user's app role assignments. Directory group membership arrives ingroupsunless the user exceeds 200 groups in a JWT, when the token carries a_claim_namesoverage pointer instead. The documentation marksoidas the immutable identifier suitable as a database key, and says not to authorise onpreferred_username,nameorupn. - Okta: custom claims can be added only on a custom authorisation server, not the org authorisation server, and each claim is set to Access Token (for OAuth 2.0) or ID Token (for OIDC) and optionally limited to selected scopes. A groups claim uses the Groups filter value type; for large directories the documentation suggests allowlists rather than searching all groups. Availability: custom authorisation servers require API Access Management, an add-on in production.
Claims-based authorisation anti-patterns
These anti-patterns map to OWASP API Security Top 10 2023 risks API1:2023 Broken Object Level Authorization, API3:2023 Broken Object Property Level Authorization and API5:2023 Broken Function Level Authorization.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Authorising on email or email domain | Emails are mutable, reusable and, from some identity providers, unverified; OIDC forbids them as unique identifiers | Key on iss plus sub (or the platform's immutable object ID); treat a domain as a hint for routing only |
| Accepting ID tokens at the API | Audience is the client, not the API; no consented scope; replay across apps | Require typ at+jwt and your own aud; reject ID tokens explicitly |
| Scope as the only check | orders:read lets the client read every order the API returns, including other tenants' (BOLA) | Object-level check: does sub or tenant own this record? |
| Trusting a claim the user can edit | Custom claims sourced from self-service profile fields (department, title) become a privilege lever | Source authorisation claims only from administrator-controlled attributes |
| Multi-issuer confusion | A sub or role from a partner or test issuer is accepted as if local | Pin issuer per API; namespace subjects as issuer plus subject |
Client-chosen sub in client credentials | RFC 9068 section 5 warns that a client able to choose its client_id could pick a value matching a privileged user's sub | Server-assigned client identifiers; distinguish app tokens by a token type claim |
| Group names as roles | Directory group names are renamed, reused and nested; overage drops the claim silently | Map stable group IDs to application roles in one place; handle overage by lookup |
| Client-side role gating only | The client must not read access tokens; hiding a button is not enforcement | Enforce every function check in the API (BFLA) |
The issuer-key dependency deserves its own line. Every claim in a signed token is only as reliable as the signing key. In the Vapor Panda ARTEX campaign, agents forged an administrator token using authentication keys recovered from an old directory export; a role claim in such a token passes every validation check. See the JWT Security Guide for algorithm and key validation.
Roles in tokens: when to embed and when to look up
A roles claim is a cache: it saves a policy lookup per request and goes stale for as long as the token lives. Decide per API.
| Approach | Use when | Trade-offs |
|---|---|---|
| Roles or permissions claim in a JWT access token | Few, coarse roles; short token lifetime (minutes); high request volume | Revocation lags by token lifetime; token size grows; readable unless encrypted |
| Opaque token plus introspection | Revocation must be immediate; claims are sensitive | Network call per request or a short cache; introspection endpoint becomes critical |
| Thin token plus policy decision point | Fine-grained or relationship-based rules; many objects; agent actions | Extra latency and a component to run; policies need testing like code |
authorization_details per transaction | One high-value action (payment, export) needs its exact parameters bound | Client and server support needed; RFC 9396 recommends an API use one form of requirement, not both |
RFC 9068 section 6 also notes that claims carried by value are visible to clients and possibly users unless the token is encrypted, and suggests omitting sensitive claims or using opaque tokens. See JWT vs Opaque Tokens and the OAuth Token Introspection and Revocation Guide. For the role model itself, see the Authorisation Models Guide.
Agent delegation tokens
An AI agent acting for a user is the case where the claims versus scopes distinction matters most, because two identities hold the same token. If the agent carries the user's raw token, APIs see only the user: no record of which agent acted, no agent-specific limits, and the user's full consent travels with a caller that injected content can steer. RFC 8693 token exchange fixes this.
Delegation, not impersonation
RFC 8693 distinguishes impersonation, where the actor is indistinguishable from the subject, from delegation, where the actor keeps its own identity and the token represents both. In a delegation token the user stays in sub and the agent appears in the act claim. Nested act claims record a chain: the outermost is the current actor and deeper ones are prior actors. The specification states that for access control a consumer must consider only the token's top-level claims and the current actor; prior actors are informational. Claims inside act describe identity only, so exp or aud placed there mean nothing.
Narrowing at each hop
In the exchange request the agent presents the user's token as subject_token and its own credential as actor_token, requesting one audience or resource and a reduced scope. Many targets in one request yield the combination of their scopes, so request one per token. A may_act claim in the user's token lets the authorisation server check that this agent is allowed to act for this user at all. See the OAuth Token Exchange and Delegation Guide and Delegation Chains and Attenuated Authority for AI Agents.
Worked scenario
A finance assistant agent helps an accounts payable clerk. The clerk's session token carries scope invoices:read invoices:approve and a roles claim of ap-clerk. The agent needs to read invoices and draft approvals, not approve them.
- The agent exchanges the clerk's token for one with
audset to the invoice API,scopeinvoices:read,subthe clerk, andactcontaining the agent's client identifier. - The invoice API validates
typ,iss,aud, signature and expiry, then checks scope for the endpoint, then confirms the clerk's cost centre owns each invoice (object check), then applies its agent policy keyed on the current actor: agents may not call approval endpoints. - When the clerk confirms an approval, the agent requests a separate token bound with
authorization_detailsnaming the invoice identifier and theapproveaction, after the user's confirmation step. The API compares the call with those details. - Logs record
sub,act,client_id,jtiand the decision, so an auditor can see that the agent read 40 invoices and the clerk approved one.
An injected "approve everything" instruction is stopped by the read-only token, the actor policy and the per-transaction grant independently. The PocketOS database deletion shows the opposite: an agent found an account-scoped token meant for domain management and used it to delete production data, because nothing in the credential limited it to its purpose. For the per-task model see Static Scopes vs Intent-Based Authorisation for AI Agents and AI Agent Service Accounts vs On-Behalf-Of Access.
Decision criteria
- Third-party or multiple clients call the API: scopes are mandatory, because claims alone cannot stop a low-trust client inheriting a powerful user's rights.
- One first-party client, many user roles: roles or entitlements claims (or a policy lookup) do most of the work; keep scopes coarse and stable.
- Machine-to-machine calls: there is no user, so
subis the client; use application permissions as scopes or roles and check that a token type claim marks it as an app token. See the Machine-to-Machine OAuth (Client Credentials) Guide. - Agents acting for users: delegation tokens with
act, one audience per token and reduced scope, plus a PDP for actions that change data. - Small vs large organisations: small teams can start with platform scopes, one administrator-controlled roles claim, short lifetimes and object checks in code; large ones should centralise group-to-role mapping, use a PDP or introspection for sensitive APIs and change-control custom claim definitions.
How to implement
- Inventory tokens and validators. For each API record the accepted issuers, token types, audience value, and every claim the code reads.
- Fix validation first. Enforce
typ, exact issuer, audience, algorithm allowlist and expiry in shared middleware; reject ID tokens. Run log-only first to find callers sending the wrong token, then enforce. - Re-key identity. Replace any lookup by email or username with issuer plus subject or the platform's immutable object ID; migrate stored ownership records.
- Separate the three checks. Scope per endpoint, role or entitlement per function, object ownership per record.
- Move authorisation claims to administrator-controlled sources. Review every custom claim definition and its source attribute; remove claims built from user-editable fields.
- Introduce delegation tokens for agents. Configure token exchange with
may_actchecks, one audience per token and reduced scope; add an actor-aware policy at each API. - Roll back safely. Keep the old validator behind a per-API feature flag and watch rejection rates by client before removing it.
Detection and measurement
Log, per request: iss, sub, client_id, act if present, jti, granted scope, the decision and the reason. Alert on unexpected issuers or audiences, ID tokens at APIs, one jti from several networks, and agent actors hitting denied endpoints. Token theft and reuse map to ATT&CK T1528 Steal Application Access Token and T1550.001 Application Access Token.
- Validation coverage: percentage of APIs enforcing
typ, issuer and audience in shared middleware (target 100%). - Identity keying: number of code paths authorising on mutable claims (target zero).
- Object check coverage: percentage of data endpoints with tested object-level authorisation.
- Role staleness window: maximum access token lifetime for tokens carrying roles, against the time to deprovision in the directory.
- Agent delegation share: percentage of agent calls using exchanged tokens with
act.
Framework mapping
| Framework | Reference | How it applies |
|---|---|---|
| NIST SP 800-53 Rev. 5 | AC-3 Access Enforcement; AC-6 Least Privilege; AU-2 Event Logging | Enforce scope, role and object checks at the API; narrow scopes and audiences; log decisions with actor |
| NIST CSF 2.0 | PR.AA-01; PR.AA-05 | Manage identities and credentials for users and services; define and enforce least-privilege permissions |
| OWASP API Security Top 10 2023 | API1, API3, API5 | Object, property and function level authorisation beyond scope checks |
| MITRE ATT&CK v20 | T1528; T1550.001 | Stolen and replayed tokens: audience restriction and sender constraint limit reuse |
| IETF RFC 9700 | Sections 2.2.1 and 2.3 | Sender-constrained, audience-restricted, privilege-restricted access tokens |
Common failure patterns
- "Admin" anywhere means admin: middleware searches
roles,groupsandscopefor a string, so a scope namedadmin.readunlocks administrator functions. - Long-lived tokens with embedded roles: a leaver's role survives in an hour-long token and a refresh token issued before deprovisioning.
- Agents with the user's full token: no
act, full scope, every audience; one prompt injection inherits everything the user consented to. - Overage ignored: users in many groups lose the groups claim, the code treats absence as "no groups", and access silently breaks or a fallback grants too much.
Practitioner checklist
- Require
typat+jwt, exactiss, youraud, an algorithm allowlist and expiry on every API. - Reject ID tokens at resource servers.
- Key ownership on issuer plus subject or an immutable object ID, never email or username.
- Check scope per endpoint, role per function and ownership per object, as three separate tests.
- Source every authorisation claim from administrator-controlled attributes only.
- Issue agents exchanged delegation tokens with
act, one audience and reduced scope. - Rotate signing keys and remove retired keys from JWKS and backups.
- Log
sub,act,client_id,jtiand the decision for every request.
Standards and references
- IETF: RFC 9068 JSON Web Token Profile for OAuth 2.0 Access Tokens
- IETF: RFC 8693 OAuth 2.0 Token Exchange
- IETF: RFC 9700 Best Current Practice for OAuth 2.0 Security
- IETF: RFC 9396 OAuth 2.0 Rich Authorization Requests
- OpenID Foundation: OpenID Connect Core 1.0
- Auth0: Enable Role-Based Access Control for APIs
- Microsoft Learn: Access token claims reference
- Okta: Customize tokens returned from Okta
Related NHI Mgmt Group resources: OAuth Scope Design Guide, Delegated vs Application Permissions, Static Scopes vs Intent-Based Authorisation for AI Agents, BOLA vs BFLA.