How to manage OAuth 2.0 scopes

In OAuth 2.0 the access given to a token for 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

In RFC 6749 the OAuth 2.0 authorisation framework defines the scope syntax as simple opaque space-delimited strings. This means that OAuth deployments can use a virtually arbitrary string for a scope value.

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 Scopes as shorthands for OpenID claims

OpenID Connect introduced standard scope values for requesting release of claims (attributes) about the logged-in user. 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 custom scope 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 carry 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), an OAuth 2.0 extension which as of 2020 is being developed at the OAuth working group, 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 permitted scopes

The Connect2id server is deliberately designed to not provide or enforce a particular method for managing the permitted scopes in a OAuth / OpenID Connect deployment.

Deployments that need to enumerate the various scopes of the managed resource servers (web APIs) can for instance list them 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 authorisation session API.

More complex or dynamic deployments can utilise a dedicated microservice to register, manage and query the scopes. The scope value as an URI can be a resource on its own, which can 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 scopes

The supported scope values can be advertised in the scopes_supported parameter of the OpenID provider / OAuth 2.0 authorisation server metadata.

This is configured in op.authz.advertisedScopes.

Sample snippet of OpenID provider metadata listing supported scopes:

{
  "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 default / permitted scopes to a client in its metadata

When a client is registered with the Connect2id server the scope metadata parameter can be set. The server will not treat the scope metadata parameter in any special way, however a grant handler (see below) can read it and apply a meaning to it, such as the default scopes for the client if none are explicitly requested, or the set of all possible scopes that may be requested.

This can be an alternative to the centrally managed scopes explained above. The downside of this approach is that it doesn't scale well and will be difficult to manage with many clients or in dynamic deployments.

Example client registration request with the scope parameter set:

POST /clients HTTP/1.1
Host: c2id.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/json

{
  "application_type" : "web",
  "redirect_uris"    : [ "https://client.example.org/cb" ],
  "client_name"      : "My Cool App",
  "scope"            : "https://scopes.c2id.com/accounts/read https://scopes.c2id.com/accounts/update"
}

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 and 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 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.

6. Managing the scope of persisted consent and for refresh tokens

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 the client. 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