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 (draft-ietf-oauth-v2-1-16, 3 September 2026) and is flagged as such everywhere it appears on these pages. It is a working-group consolidation in progress, not a published standard; nothing here should be read as saying otherwise.

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. orders:write does not mean "the user is an editor" — it means "this client was granted permission to write orders, for as long as this token is valid, for this specific delegation". The same user might hold a completely different scope through a different client, or none at all.

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 (scope claim, or the introspection response)

Claim

A fact asserted by the issuer about the subject — an authentication event property (ID token: sub, auth_time, acr, amr) or an attribute (name, email, group membership)

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:read and nothing else.

  • Lets a resource server enforce per-endpoint scope checks mechanically, mapping HTTP verbs to :read/:write scopes rather than hand-maintaining a permission matrix.

  • Scales sanely as new resources are added, since each new API surface adds its own resource:action pair 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, authorization_details, audience) — cheap for the resource server to enforce, but the decision is frozen at issuance and cannot react to context that only exists at request time (current account balance, time of day, current risk score)

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.

Decision tree: scope, claim, RAR, or not in the token at all

flowchart TB q["New piece of authorization data\nneeded by a resource server"] --> stable{"Is it a stable\nyes/no capability grant,\nknown at consent time?"} stable -- yes --> simple{"Is a flat\nresource:action\nenough to express it?"} simple -- yes --> scope["Model as scope"] simple -- no, needs structured\ndetail --> rar["Model as authorization_details\n(RFC 9396, RAR)"] stable -- no --> fact{"Is it a fact about\nthe authentication event\nor the subject itself?"} fact -- yes --> claim["Model as a claim\n(ID token or access token)"] fact -- no --> dynamic{"Does it depend on\nruntime/business context\nthat changes after issuance?"} dynamic -- yes --> pdp["Do not put it in the token --\nquery a policy decision point\nat request time"] dynamic -- no --> claim