An OAuth scope is a short string, such as openid or invoices:read, that names one permission an application is asking for and that is associated with the access token if the request is approved. The client puts the scopes it wants in the scope parameter of the authorization request, the authorization server decides which of them to grant (sometimes fewer than were requested), and the API receiving the token checks that the scope it needs is on it. Scopes are the coarse permission layer of OAuth 2.0 and OpenID Connect, and they are separate from claims, roles and the finer-grained permissions that an API enforces itself.
What is an OAuth scope?
The OAuth 2.0 specification defines the scope parameter as a list of space-delimited, case-sensitive strings, each one defined by the authorization server (IETF, RFC 6749, “The OAuth 2.0 Authorization Framework”, section 3.3, 2012). That is all the standard says about the format, so the meaning of any given scope is a contract between your authorization server and the APIs that trust it. If a client omits the parameter, the server either applies a predefined default or rejects the request, which is the behavior Keycloak’s default client scopes build on.
Two properties matter in practice. First, the authorization server may issue a token with a narrower scope than the client asked for, and when it does, it must say so in the token response. A client that assumes it got everything it requested will fail in confusing ways, so read the scope that came back. Second, a scope limits what the token can be used for, not what the user is allowed to do. An API that checks scopes will refuse a delete made with a token that only has invoices:read, but having invoices:read on the token also does not mean this particular user may read every invoice.
What are the standard OpenID Connect scopes?
OpenID Connect adds a small set of standard scopes on top of OAuth. openid is required and turns an OAuth request into an OpenID Connect sign-in, which is what causes an ID token to be returned. The others ask for groups of user information (OpenID Foundation, “OpenID Connect Core 1.0”, section 5.4, 2014):
| Scope | What it requests |
|---|---|
openid |
Signals an OpenID Connect request and is required to get an ID token, which always contains sub |
profile |
Name, preferred username, picture, locale and similar profile claims |
email |
email and email_verified |
address |
The user’s postal address |
phone |
phone_number and phone_number_verified |
offline_access |
A refresh token that can be used when the user is not present (defined in section 11 of the same specification) |
offline_access deserves care. It asks for a long-lived way to keep getting access tokens after the user has left, which is a bigger grant than a short-lived access token, so request it only from clients that really need background access, and be prepared to explain it on a consent screen. OpenID Connect also says the server must ignore offline_access unless the request includes prompt=consent (or another condition that permits offline access), so a client that requests it without consent may silently get no refresh token.
What are API scopes and how should you name them?
Beyond the OpenID Connect scopes, you define scopes for your own APIs. A pattern that holds up well is resource:action, for example invoices:read, invoices:write and reports:export. It reads clearly in logs and on consent screens, and it lets an API check for the one scope it cares about without parsing anything.
Granularity is the design decision that matters most. Too coarse (a single api scope) and every client receives the same broad access, which makes least privilege impossible. Too fine (one scope per endpoint) and you end up with hundreds of scopes that nobody can reason about, and clients that need a long list just to function. A workable middle is one scope per resource and access level, with an occasional extra scope for an operation that is notably riskier than the rest, such as exporting data or acting for another user.
Here is what a request and the resulting token look like in practice. The client asks for two standard scopes and one API scope:
GET /authorize?response_type=code&client_id=billing-ui
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&scope=openid%20email%20invoices%3Aread
If the server grants all three, a JWT access token for your API might carry claims like these:
{
"iss": "https://auth.example.com/realms/acme",
"aud": "invoices-api",
"sub": "8a1c0f2e-5d3b-4c71-9b0e-2f6a7d9e1c34",
"scope": "openid email invoices:read",
"exp": 1790000000
}
The aud claim says which API the token is meant for, and the scope claim says what it may do there. They are separate checks: audience stops a token for one API being replayed against another, and scope limits what the token can do at the API it was issued for. Resource indicators (IETF, RFC 8707, “Resource Indicators for OAuth 2.0”, 2020) let a client say which API it wants the token for when it asks.
What is the difference between scopes, claims, roles and permissions?
These four terms are easy to mix up, and they sit on different layers:
- Scope: a permission a client requests and a token carries. It describes what the application may do on the user’s behalf.
- Claim: any piece of information inside a token or returned by the userinfo endpoint, such as
email,suborexp. A scope can cause claims to be included (theemailscope releases theemailclaim), but a claim is just data, not a permission by itself. - Role: a label attached to a user, such as
adminorbilling-manager, that groups what that person may do. Roles describe the user, and scopes describe the client’s request. - Permission: the actual rule your API enforces on a specific action, often computed from a role, a resource owner and a scope together.
A useful way to hold this together: the scope says what the app was allowed to ask for, the role says who the user is, and the API combines both with its own rules to decide a given request. Servers often put the granted scope into the access token as a scope claim, which is how the JWT profile for access tokens expects it, as a space-separated string; some providers use scp instead, sometimes as an array, so check what your issuer emits (IETF, RFC 9068, “JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens”, 2021). If you want to see how this plays out for one product, our post on Keycloak client scopes versus roles covers the Keycloak specifics, and ID token vs access token explains which token should carry which information.
Who enforces scopes?
The resource server, which means your API, enforces them, not the client application. A client can ask for any scope it likes, and a malicious client may request scopes it should not have, so the checks that count happen where the data lives. Each API endpoint should verify the token’s signature, issuer, audience and expiry, and then confirm that the scope it requires is present. Our guide to verifying a Keycloak-issued access token on the backend shows that sequence, and JWT best practices for developers covers the surrounding checks.
Where an API needs decisions that depend on the specific record or the situation (this user may edit only invoices from their own team), scopes alone are not enough and you add real authorization logic. The Keycloak side of that is described in Keycloak authorization services policy types.
How do you apply least privilege and consent to scopes?
Request the smallest set of scopes the feature in front of the user needs, and ask for more later when the user reaches a feature that needs it (often called incremental authorization). For third-party applications, show the user a consent screen that describes each scope in plain words, because that screen is the control that lets the user say no. For first-party applications that your organization owns, consent is usually skipped, but the least-privilege reasoning still applies, since a leaked token with fewer scopes does less damage.
Scope sets also drift over time. Review which clients hold which scopes on a regular schedule, for example alongside your access reviews, remove the ones that are no longer used, and treat a request for a broad scope on a new client as something that needs a reason.
Can you change scopes at token time?
Many identity providers let you adjust the granted scopes while the token is being built. Auth0 documents this in its Actions feature, where code running after login can call api.accessToken.addScope and api.accessToken.removeScope (Auth0 documentation, “Actions Triggers: post-login, API object”, 2026). Whichever provider you use, check the token’s audience before adding a scope and never build scope strings from user input.
Keycloak handles most of this through configuration rather than a login script (Keycloak documentation, “Server administration guide: Client scopes”). Each client has default client scopes, which are always included, and optional client scopes, which are only included when the client asks for them in the scope parameter. Client scopes can also carry protocol mappers that add claims, and scope-to-role mapping restricts which roles appear in the token. Keycloak’s scopes are static per client, so granting a scope based on who the user is takes role scope mappings, a custom protocol mapper, or the dynamic scopes feature, and not a per-login rule. To add an API scope such as invoices:read, create a client scope with that name under Client scopes and assign it to the client as an optional scope, so it is included only when the client asks for it. The related setting to know is Full scope allowed. It is on by default for new clients, and it puts all of the user’s roles into every token issued to that client; turning it off and listing the relevant roles keeps tokens small and narrow.
What are the common OAuth scope mistakes?
- Trusting the requested scope instead of the granted scope. What the client requested is not necessarily what it was granted. The scope in the token or token response is what was actually approved, and both clients and APIs should rely on that.
- Using the ID token to call an API. The ID token is meant for the client to learn who signed in. APIs should accept access tokens with the right audience and scope, not ID tokens.
- Putting authorization data only in scopes. Scopes are coarse, so record-level rules end up either too permissive or absent. Combine them with roles and API-side checks.
- Handing out
offline_accessby default. Long-lived refresh tokens widen the window in which a stolen token can be used. - Letting scope names become a free-for-all. Without a naming convention and an owner, you get near-duplicates (
read_invoices,invoices.read,invoice-read) and nobody knows which is authoritative.
To put this into practice, list the permissions your APIs actually distinguish, name them in a resource:action pattern, make each one an optional scope on the clients that need it, and have every API check the granted scope and audience. The upstream Keycloak model we run covers applications and clients and role-based access control with standard OAuth and OIDC behavior, so nothing here is specific to one vendor.
FAQ
What is an OAuth scope?
A scope is a named permission that a client requests in the authorization request and that the authorization server records on the access token if it approves. The API reads the scope from the token to decide whether the call is allowed.
What is the difference between OAuth claims and scopes?
A scope is a permission being requested, and a claim is a piece of data inside a token. Some scopes control which claims are released (the email scope releases the email claim), and the granted scopes are often themselves carried in a scope claim.
What is the difference between an OAuth scope and a permission?
A scope is a coarse permission granted to an application by the token, and a permission is the specific rule that an API enforces on an action. An API usually decides a request by combining the scope, the user’s roles and the resource involved.
What is the openid scope?
It is the scope that makes a request an OpenID Connect sign-in rather than plain OAuth. Without it, no ID token is returned.
What does offline_access do?
It requests a refresh token that remains usable when the user is not present, so the application can keep getting access tokens in the background. Because it lasts longer, request it only when the feature needs it.
Who checks the scope on a token?
The resource server, meaning your API. The client application cannot be trusted to limit itself, so each API endpoint must check the signature, audience, expiry and required scope on every request.