Introduction to Scopes
Scopes and how they relate to claims
In this article we take a closer look at scopes, and discuss some aspects of how to handle them.
What are Scopes?¶
A scope is a grouping of claims - no more and no less, although the name may lead to other interpretations. You can think of them as access ranges or "scope of access". Depending on if you use OAuth or OpenID Connect they are more or less defined.
OAuth defines scopes in a very open and interpretation forgiving way. A scope is a string that may represent a resource the Client requests access to.
In OpenID Connect however the scope is defined in a more well defined manner, and the specification even define pre-defined scopes that have meaning in the OpenID Connect context.
Claims are not part of OAuth, but are added in OpenID Connect. Therefore it is only the OpenID Connect specification that maps pre-defined claims to pre-defined scopes.
The OpenID Connect specification only defines a single set of standard scopes:
The anatomy of scopes¶
scope request parameter is a space separated, case-sensitive list of scopes. So it may contain several scopes, even though the singular parameter name indicates otherwise.
The specification does define formats and server handling, but does not prescribe any particular contents or behaviors beyond that. This means that it is very easy to customize your own scope architecture, but also means that you should do it somewhat carefully.
Scopes in tokens¶
Scopes are only present in the OAuth Access Tokens. The ID Token never contains scopes, but instead contains the resolved claims that belong to the issued scopes. This is an important difference, since the ID Token is intended to be used and read by the Client, the Access Token is intended to be read and understood by the API. The Access Token should be treated as opaque to the Client.
This means that the Scopes in the Access Token may be used as coarse grained access control attributes.
An API defines an invoice service, with two main operations: read and write. Some Clients only need read access since they are intended for end-users to view their invoices, while other Clients need write access, for internal Clients that create the invoices.
A scope and claim pattern could look as follows:
When a read Client requests an access token, the claim
customer-number will be added and populated with the customer number that the invoices should be shown for (given that claims are mapped to the access token). The token also contains the
When a write Client requests an access the claim
invoice-center is also added to the token with a value indicating what department the invoices are handled from. The scope
write is also part of the access token.
This provides some benefits, it's now possible to provide different degrees of access control when the API call is being made. More on that in the following sections.
When requesting scopes, make sure that your applications only ask for what they need. If all you want to do is identify a user, don't request more than you need to assert it. Consider the use case, and select accordingly.
Default or fail
Make sure to request something, or be prepared for the consequences. According to the specification, if a scope is not specified in the request, the server can either return a default scope, return something else, or completely fail the request.
It is also possible to request only single claims, possibly from different scopes. If that is what the application needs and no more - remember, the point is to be as exclusive as possible.
This also affects consent, as only requesting what you need, you are more likely to gain user consent when required since users are more likely to grant access for limited, clearly-specified scopes of access.
Scopes and claims in use¶
When used in the authorization flows, the client sends an authorize request, including the scope or scopes or specific claims it needs (and ideally, only those it needs) to the OpenID Connect provider. This eventually results in the issuance of one or many tokens, which contains claims according to the requested scopes.
Claims, not scope
If the request is for less than the full scope, the returned token contains a list of claims, and does not include the scope itself.
When users provide consent, it is possible, maybe even likely, that they will not consent to everything. If so, the application may request scopes that the user has not consented to, and hence will not be provided in the response.
In effect, server overrides the scope request and does not provide what was asked for - however, the server must still include scope in its response, informing the client of what scope was actually issued.
Therefore it is important that the application requesting a scope (or claim) has a process in place for how to act when what you requested is not what you receive. This may include asking for additional consent, or just informing the user about reduced functionality, or maybe even a complete re-authorization.
Using scopes for Authorization¶
When the client makes a request to the API, it's now possible to make a coarse grained authorization decision in a gateway. The gateway checks if the token is valid by communicating with the Token Service to validate the token. If using the Phantom Token Flow, the Token Service responds with a JWT representation of the same token. The gateway can then check if any of the required scopes are present in the token.
In the example above this means, for the Invoice API, either
write needs to be present. The gateway doesn't need to check in depth of the operation is matching the scope, that is left for the API to resolve later, but it can stop unwanted requests early on, at a low cost. This keeps the complexity of the gateway to a minimum but provides a robust layer of authorization in front of the API.
The API will obviously be required to check that the operation matches the scope required and that the claims it needs are present. But this is then part of a more fine grained authorization decision.
One problem that may occur when using scopes stems for the intuitive, but wrong way, of thinking about what scopes are. We call this problem scope explosion - the proliferation of scopes beyond the point of manageability.
This normally occurs when an enterprise tries to solve the need for multiple roles by using scopes as a sort of role identifier, instead of a more fine tuned approach. Often by assigning permissions to users by assigning scopes basically as a type of roles.
This way of working necessitates new roles for each application and possibly multiple roles for each user, leading to an non-scalable and unmaintainable "explosion" of scopes, including overlaps and redundancies.
The Prefix Scope¶
Curity has a special type of scope called the Prefix Scope. Just like the name suggests the scope itself is just a prefix, and the final scope depends on what the client requests.
The prefix scope is useful when there are resources that dynamically need to be allocated. The server can implement policies to issue or not issue these scopes when needed.
If consent is enabled, the user will be presented with the suffix
diary in the above example to consent to.
A prefix scope cannot carry any claims, instead the value of the suffix is considered to be a claim of sorts, which is why this is part of consent.
A common use-case for this could be when the user needs to consent to a transaction, the prefix
transaction- is used with an id added
transaction-100.311 to show the user what is going on.
The API's then can allow access to particular resources based on if the exact scope is present.
Prefix Scopes and Claims
A prefix scope cannot be mapped to any additional claims. The scope itself will be consented to.
The Curity Solution¶
Curity provides the full benefits of OAuth and OpenID Connect standards, but also offers additional functionality to combat the risk for scope explosion.
There is also special handling of claims and scopes, such as mapping claims to specific clients and custom groupings that allow for greater flexibility and a more manageable architecture.
For more information, see the mentioned articles above, or the Curity Developer Portal