Scopes, Claims and Permissions
|
This section documents OAuth 2.0 (RFC 6749) as amended by the OAuth 2.0 Security Best Current Practice (RFC 9700 / BCP 240), the OAuth 1.0 Protocol (RFC 5849) for historical context, and OpenID Connect Core 1.0, as published at the IETF Datatracker and the OpenID Foundation specifications — and, on the Spring pages, Spring Boot 4.1.x and Spring Security 7.1.x as published at the Spring Security reference documentation — which are the references these pages are written and verified against. OAuth 2.1 is still an Internet-Draft ( This content was generated with the assistance of AI and should be verified against those official specifications before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
scope is the single most misused word in OAuth. It looks like a permission and gets typed like a role, but the
protocol defines it as something narrower and more specific: a request. This page pins down what scope
actually is, contrasts it with claims, roles and entitlements, and covers the mechanisms that extend delegated
authorization beyond a flat scope string — audience restriction, Rich Authorization Requests, step-up, and where
the authorization decision itself should be made.
scope as a request for delegated capability
RFC 6749 §3.3 defines scope as a space-delimited list of case-sensitive strings the client sends requesting
some subset of access, which the resource owner then consents to (in full or in part) and the authorization
server grants — echoed back in the token response, possibly narrower than requested, never wider. That
sequence — client requests, owner consents, server grants — is the entire contract. Nothing in the spec says a
scope string names a role the resource owner holds, or a permission the resource owner is entitled to outside
this specific delegation; it names what this client is being allowed to do on the owner’s behalf, right now.
|
A scope value is not a role. |
Scopes vs. claims vs. roles vs. entitlements
| Concept | What it represents | Where it belongs |
|---|---|---|
Scope |
A capability this specific client was granted, for this specific delegation |
The access token ( |
Claim |
A fact asserted by the issuer about the subject — an authentication event property (ID token: |
The ID token (facts about authentication) or the access token (facts the resource server needs to make its own decision, e.g. a tenant identifier) |
Role |
A named bundle of permissions assigned to a subject within an application’s own authorization model, independent of any single OAuth grant |
The resource server’s own user/role store, optionally surfaced as a claim if the resource server chooses to trust the issuer for role assignment |
Entitlement |
A fine-grained permission or attribute typically resolved from a separate identity-governance or policy source, often more dynamic than a role (e.g. "may approve purchases up to $10,000") |
A policy engine or entitlement service the resource server (or a policy decision point) consults — essentially never appropriate to put directly in a token |
The practical rule: scope answers "what was this delegation for", claims answer "what facts does the issuer assert", and roles/entitlements answer "what is this subject fundamentally allowed to do in our system" — a question that usually should not be fully answered by the contents of a bearer token at all, since token contents are cached at issuance time and an entitlement can change mid-token-lifetime.
Naming conventions
There is no single mandated scope-naming scheme, but the pattern that keeps a growing API surface manageable is
resource:action (or resource.action) — orders:read, orders:write, orders:admin — rather than a single
opaque scope per feature or, worse, one scope that grants everything. A resource-and-verb convention:
-
Makes incremental authorization legible: a client can request exactly
orders:readand nothing else. -
Lets a resource server enforce per-endpoint scope checks mechanically, mapping HTTP verbs to
:read/:writescopes rather than hand-maintaining a permission matrix. -
Scales sanely as new resources are added, since each new API surface adds its own
resource:actionpair instead of overloading an existing one.
Incremental authorization and down-scoping
Incremental authorization means a client requests only the scope it needs for the feature the user is using right now, and asks for more later — via a fresh authorization request — only when the user reaches a feature that needs it, rather than front-loading every scope the application might ever use into the first consent screen. This keeps the consent prompt legible (a user can actually reason about "read your calendar" vs. a wall of ten scopes) and limits what a compromised token can do to whatever was actually needed at the time.
Down-scoping is the token-exchange-time version of the same idea: an authorization server (or a party doing token exchange, RFC 8693) can mint a new token with a strict subset of an existing token’s scope — for example, a backend service that holds a broad token internally but calls a downstream service with a narrower one, so a compromise of the downstream call cannot pivot back to the broader scope. See Token Exchange and Assertion Grants.
Audience restriction and resource indicators (RFC 8707)
Plain OAuth 2.0 does not standardise which resource server a token is good for beyond whatever the authorization server decides at issuance — which pushes deployments with more than one resource server toward either one broad token trusted everywhere (bad: any one resource server that mishandles it can replay it against another) or ad hoc, non-interoperable audience conventions.
RFC 8707 fixes this by letting the client name the target resource explicitly, at request time:
POST /oauth2/token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=SplxlOBeZQQYbYS6WxSbIA&
redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb&
resource=https%3A%2F%2Forders-api.example.com&
resource=https%3A%2F%2Finventory-api.example.com
Each resource parameter is an absolute URI identifying one target resource server; the authorization server
either mints one token whose aud covers exactly those resources, or — if it chooses not to allow one token to
span multiple audiences — returns distinct tokens per resource. Either way, a resource server that checks aud
(it always should — see the validation checklist on
JWT and the JOSE Family) now rejects a token minted for a different
resource, closing the confused-deputy gap RFC 9700 §4.9 describes.
Rich Authorization Requests (RFC 9396)
A flat scope string cannot express transaction-level detail — "read orders" says nothing about which
account, which amount, or which specific action within a broader capability. RFC 9396 defines
authorization_details, a structured JSON array sent alongside (or instead of) scope, where each element
names a type and whatever fields that type needs:
{
"authorization_details": [
{
"type": "payment_initiation",
"actions": ["initiate"],
"locations": ["https://payments.example.com"],
"instructedAmount": {
"currency": "EUR",
"amount": "125.50"
},
"creditorAccount": {
"iban": "DE02100100109307118603"
}
}
]
}
This lets the resource owner consent to — and the resource server enforce — something far more specific than a scope string could ever express: not "this client may initiate payments" but "this client may initiate this one EUR 125.50 payment to this account". RAR is the mechanism financial-grade deployments (FAPI 2.0) reach for when scope granularity runs out.
Step-up authentication (RFC 9470)
Some operations need stronger proof of the resource owner’s presence than whatever authentication happened when
the current token was issued — approving a high-value payment after a session that started with a lightweight
login, for instance. RFC 9470 defines a standard way for a resource server to signal this back to the client,
via the WWW-Authenticate header’s insufficient_user_authentication error and an acr_values (or max_age)
hint naming what would satisfy it:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="insufficient_user_authentication",
error_description="A higher authentication level is required to access this resource",
max_age=60,
acr_values="urn:mace:incommon:iap:silver"
The client responds by sending the resource owner back through authorization with that acr_values/max_age
requirement attached, forcing a fresh, stronger authentication event before the authorization server issues a
token that satisfies it. What "stronger" means in practice — adding a TOTP or push-approval step on top of an
existing session — is covered on
Authentication Methods: 2FA and
Passwordless.
Where the authorization decision should live
Three places can, in principle, decide "is this specific call allowed":
| Location | Trade-off |
|---|---|
Authorization server (AS) |
Encodes the decision into the token at issuance time (scope, |
Resource server (RS) |
Enforces coarse checks itself (does the token’s scope cover this endpoint and verb) — simple and fast, but every resource server re-implements its own authorization logic, and cross-cutting policy changes mean redeploying every RS |
A dedicated policy engine (policy decision point) |
The RS asks a separate policy decision point (PDP) for a yes/no on each request, given the token’s claims plus whatever runtime context the PDP has — centralises policy authoring and lets it react to context the AS never saw, at the cost of another network hop and another moving part |
Coarse, stable, delegation-shaped decisions (does this client have any business calling this API at all) belong
at the AS, expressed as scope or authorization_details. Fine-grained, context-dependent, frequently-changing
business rules (can this user approve this purchase given today’s limits) are usually better served by a
dedicated policy engine than baked into a token that was issued minutes or hours earlier. OPA/Rego, Cedar and
AuthZEN are the current names in that space — named here only as adjacent work; this reference does not
document any of them.