Introduction to Scopes
Scopes and their relation to claims
In this article we take a closer look at scopes and discuss how to handle them properly.
A scope is simply a grouping of claims, although the name sometimes lends itself to other interpretations. You can think of claims as access ranges or “scopes of access”. Depending on whether you use OAuth or OpenID Connect, scopes are are to a greater or lesser degree defined.
OAuth defines scopes in a very open and forgiving way. In OAuth, a scope is a string that may represent a resource the Client requests access to.
In OpenID Connect, however, a scope is defined in a more clear-cut manner, and the specification even defines 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:
scope request parameter is a space separated, case-sensitive list of scopes. So it may contain several scopes, even though the singular parameter name may suggest otherwise.
The specification defines 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. It also means that you should customize with care.
Scopes are only present in 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. The ID Token is intended to be used and read by the Client, while 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 so end-users can view their invoices, while other Clients need write access for the 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 associated with the invoices to be shown (given that claims are mapped to the access token). The token also contains the
When a write Client requests access, the claim
invoice-center is also added to the token with a value indicating the department the invoices are handled from. The scope
write is also part of the access token.
This provides some benefits, as it is now possible to provide different degrees of access control when the API call is 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 failMake 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, something else, or completely fail the request.
It is also possible to request only single claims, even 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. When you only request what you need, you are more likely to gain user consent since users are more likely to grant access for limited, clearly specified scopes of access.
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 scopeIf 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, they may not be intending to grant consent to everything, and the application may request scopes that the user has not consented to.
In such cases, the server overrides the scope request and does not provide what was asked for. However, the server must still include the scope in its response, informing the client 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 respond when what was requested is not what was received. This may include asking for additional consent, informing the user about reduced functionality, or perhaps requiring a complete re-authorization.
When the client makes a request to the API, it is 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 is 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 if the depth of the operation matches the scope. That is, that it is left for the API to resolve later, but the gateway 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 from an intuitive but incorrect 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 address the need for multiple roles by using scopes as a sort of role identifier instead of using claims to take a more fine-tuned approach.
To avoid scope explosion, it is best not to attach scopes to users. Doing so necessitates new roles for each application, sometimes even multiple roles for each user, leading to an non-scalable and unmanageable “explosion” of scopes, including overlaps and redundancies.
Curity has a special type of scope called the Prefix Scope. As 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 need to be allocated dynamically. The server can implement policies to issue or not to issue these scopes as needed.
If consent is enabled, the user will be presented with a 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 a matter of consent.
An example of this is when the user needs to consent to a transaction. The prefix
transaction- can be used with an ID added
transaction-100.311 to show the user what is going on.
If the exact scope is present, the API can then allow access to particular resources.
Prefix Scopes and ClaimsA prefix scope cannot be mapped to any additional claims. This would result in the scope itself being consented to.
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
Let’s Stay in Touch!
Get the latest on identity management, API Security and authentication straight to your inbox.