How to manage OAuth 2.0 scopes

In OAuth 2.0 the access that a token has to a protected resource is represented by the concept of "scope". This guide explains the best practises for managing scopes with the Connect2id server.

1. Defining scopes

The OAuth 2.0 authorisation framework in RFC 6749 defines the scope syntax as opaque space-delimited strings. This means that OAuth deployments are flexible to define the content of scope values.

The scope values can be used to represent:

  • Actions to be performed at the web API (protected resource)
  • Roles / entitlements

What are the best practises?

  • Choose an appropriate granularity -- Make sure the scopes are granular enough to match the types of access granted to a particular resource.

  • Give a name space for the scopes for each protected resource -- To make sure the scope values for two different resources don't accidentally clash with one another.

Use URIs or URNs to define the scope values:

  • The URI domain together with a path component provides natural namespacing for the organisation and its various resources (web APIs).

  • A path component can be used to signify an action or classes of actions.

  • Optional parameters to the authorisation, such the amount to transfer in a financial transaction, can be specified in the URI query string.

  • You can use standard URI tools to construct and parse the scope values.

Example scope values:

https://scopes.c2id.com/accounts/read
https://scopes.c2id.com/accounts/update
https://scopes.c2id.com/accounts/delete
https://scopes.c2id.com/accounts/transfer
https://scopes.c2id.com/sec-events/send
https://scopes.c2id.com/sec-events/listen

1.1 Scope shorthands for OpenID claims

OpenID Connect introduced a set of standard scope values for requesting release of claims (attributes) about the logged-in user to client applications. For example, the profile scope value is used to signify a request for the following OpenID claims: name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale and updated_at.

The Connect2id server allows you to configure your own custom scope value to claim mappings.

For example, to define a scope value shorthand for releasing organisation-related claims:

https://scopes.c2id.com/org_profile=roles,supervisor,employee_number

1.2 Scopes with parameters

The scopes can also include optional parameters, for example to let the user authorise a particular parameter related to an action requested by the client application.

If the scope is defined as an URI, such optional parameters can be naturally encoded in the query string:

https://scopes.c2id.com/accounts/transfer&dest=org4519

Tip: When a scope with parameters gets consented, this is typically a one time operation, so therefore the consent should not be persisted (long_lived=false), i.e. kept transient.

The content of Rich Authorisation Requests (RAR), a recent OAuth 2.0 extension, can also be mapped to or from a URI-encoded scope.

For example the RAR parameter

{
   "type": "payment_initiation",
   "locations": [
      "https://example.com/payments"
   ],
   "instructedAmount": {
      "currency": "EUR",
      "amount": "123.50"
   },
   "creditorName": "Merchant123",
   "creditorAccount": {
      "iban": "DE02100100109307118603"
   },
   "remittanceInformationUnstructured": "Ref Number Merchant"
}

can be represented as the scope value

https://example.com/payments?
 type=payment_initiation
 &instructedAmount.currency=EUR
 &instructedAmount.amount=123.50
 &creditorName=Merchant123
 &creditorAccount.iban=DE02100100109307118603
 &remittanceInformationUnstructured=Ref%20Number%20Merchant

2. Managing the supported scopes

The Connect2id server gives OAuth 2.0 / OpenID Connect deployments the freedom to devise their own methods to manage the scopes that are being supported.

The scope values that managed resource servers (web APIs) support can for instance be listed in a static file in a centrally managed git repo which is pulled from the various OAuth grant handlers of the Connect2id server, such as the login page handler.

A dedicated microservice may also be used, to register, manage and query the scopes. The scope value as an URI can be a resource on its own, to be queried via HTTP GET.

GET /accounts/read HTTP/1.1
Host: scopes.c2id.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6

The scope details:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "value"       : "https://accounts.c2id.com/accounts/read",
  "resource"    : "https://accounts.c2id.com/v1/",
  "action"      : "read",
  "grants"      : [ "authorization_code", "client_credentials" ],
  "registered"  : 1592219549,
  "description" : "Read a user's account information"
}

A 404 status code will indicate an invalid scope value. Caching directives can be used to indicate how often the grant handlers should refresh the scope information.

3. Advertising supported scopes

The scope values that an OAuth 2.0 authorisation server / OpenID provider supports are typically advertised in the scopes_supported metadata parameter.

This is configured in op.authz.advertisedScopes.

Sample OpenID Provider metadata snippet with supported scope values from OpenID Connect:

{
  "issuer"                 : "https://fc2id.com",
  "jwks_uri"               : "https://fc2id.com/jwks.json",
  "registration_endpoint"  : "https://fc2id.com/clients",
  "authorization_endpoint" : "https://fc2id.com/login",
  "token_endpoint"         : "https://fc2id.com/token",
  "scopes_supported"       : [ "openid",
                               "profile",
                               "email",
                               "address",
                               "phone",
                               "offline_access" ],
  ...
}

Note that for privacy or other reasons this list can be a subset the scopes that are actually supported.

4. Assigning allowed / default scopes to a client in its metadata

When a client is registered with the Connect2id server its scope metadata parameter can be set, as shown in this example.

If the client is using an OAuth 2.0 authorisation code or implicit grant (flow), the scope metadata determines what scope values the client is allowed to request. Scope values that are not present in the client metadata will be filtered out and will not appear in the consent prompt that gets presented to the user. If the scope client metadata is not set, the scope values in authorisation requests made by client to the Connect2id server will appear without any filtering in the consent prompt.

This behaviour for the code and implicit grants can be disabled by the op.authz.limitToRegisteredScope configuration property.

For clients using other OAuth 2.0 grants, such as the client credentials grant, the treatment of the scope metadata parameter is determined by its grant handler. The handler plugins for these grants provided by Connect2id will bound the requested scope values to the registered in the client metadata. If the client omits the scope parameter in the token request, the requested scope will default to the values that are found in the scope client metadata parameter. This is explained in the sections below on these grants types.

5. Handling scopes in OAuth 2.0 authorisation requests

The Connect2id server exposes the scope requested by the client to the handler of the OAuth grant type.

When consenting a requested scope the handler and the end-user have choices along four dimensions:

  1. Allow / deny: allow all, allow some, deny all.
  2. Long-lived / transient consent: if the consent is set as long-lived (long_lived=true) it will be persisted, i.e. remembered for subsequent requests; long-lived consent is also required in order to issue a refresh token (refresh_token.issue=true). If the consent if for one time access or operation, it should be marked as transient (long_lived=false). Scope values with parameters, e.g. authorise transfer of X amount of money, also typically fall into this category.
  3. Explicit / implicit consent: explicit consent is term for consent driven by end-user input, implicit by policy or some other authorisation server rule.
  4. Grant non-requested scope values: the handler can also allow scope values which are not explicitly requested by the client.

5.1 Code, implicit grants

For the browser (user-agent) based OAuth 2.0 grants, authorisation code and implicit, the authorisation session API will expose the requested scope (after removing all values that the client is not allowed to request) in the consent prompt object.

The requested scope values will be segmented into two sets - scope values for which previous end-user consent exists on record (consented) and values for which there is none (new). The end-user can thus be given a clear presentation of previous and newly requested consent, with the possibility to deselect some of the previously consented scope values.

If the op.authz.includeOtherConsentedScopeAndClaimsInPrompt configuration setting is enabled the handler will also be presented with all existing consented scope values which the Connect2id server has on record for the given client and end-user.

If the client registration has the scope metadata parameter set, the values will appear under client.scope. How those values get treated is up to the grant handler.

Snippet of an example consent prompt:

{
  "type"        : "consent",
  "sid"         : "g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE",
  "display"     : "popup",
  "sub_session" : { ... },
  "client"      : { "client_id"        : "8cc2043",
                    "client_type"      : "confidential",
                    "application_type" : "web",
                    "scope"            : [ "https://scopes.c2id.com/accounts/read",
                                           "https://scopes.c2id.com/accounts/update",
                                           "https://scopes.c2id.com/accounts/delete",
                                           "https://scopes.c2id.com/accounts/transfer" ] },
  "scope"       : { "new"       : [ "https://scopes.c2id.com/accounts/read",
                                    "https://scopes.c2id.com/accounts/update" ],
                    "consented" : [ "openid" ] },
  "claims"      : { ... }
}

5.2 Client credentials grant

Handling of the OAuth client credentials grant is done by a dedicated SPI. The default provided implementation bounds the permitted scope values to those present in the scope client metadata parameter.

To plug a different policy you can implement a custom handler.

5.3 Resource owner password grant

Handling of the OAuth password grant is similarly done by a dedicated SPI, with the possibility to plug a custom policy.

5.4 JWT and SAML 2.0 bearer assertion grant

As you've probably guessed, the JWT and the SAML 2.0 bearer assertion grant handler follow the exact same SPI approach.

Administrators and end-users can be provided with a UI screen to modify the scope in persisted consent and for any linked refresh tokens that have been issued to clients. This is done via the authorisation store API.

The persisted consent for a given client and end-user is obtained with an HTTP GET request to the authorisations resource.

GET /authz-store/rest/v2/authorizations?subject=alice&client_id=65564eb0058d HTTP/1.1
Host: c2id.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6

The Connect2id server will then return the authorisation record, or HTTP 404 if none is found.

Snippet of an example authorisation record, the currently active scope values are listed in the scp parameter:

HTTP/1.1 200 OK
Content-Type: application/json

{
 "sub" : "alice",
 "cid" : "65564eb0058d",
 "scp" : [ "openid",
           "https://scopes.c2id.com/accounts/read",
           "https://scopes.c2id.com/accounts/update" ],
 ...
}

A PUT request can be used to remove some of the scope values.

Revoking the entire consent is done by POSTing a revocation.

POST /authz-store/rest/v2/revocation HTTP/1.1
Host: server.example.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/x-www-form-urlencoded

subject=alice&client_id=1d6a3150fd3c