Client credentials grant handler SPI

1. Overview

The OAuth 2.0 client credentials grant is intended for applications or services that act on their own behalf (instead of on behalf of an end-user, the common OAuth 2.0 case), to make requests to a protected resource with an access token.

For a client to make an access token request on its own behalf it must be explicitly registered for the client_credentials grant. The request is authenticated with the client's credentials, such as client_id and client_secret (hence the name of the grant), and may optionally specify an explicitly requested scope.

Note, the response doesn't include a refresh token. If the obtained access token has expired, the client must simply repeat the request to get a new one.

1.1 Example token request with a client credentials grant

The client simply submits its own authentication (client_secret_basic in this example), indicating the grant type in the body:

POST /token HTTP/1.1
Host: c2id.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

The authorisation server checks the client credentials and if they are valid, returns an access token with scope (read, write) and expiration determined by the server's policy:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token"  : "2YotnFZFEjr1zCsicMWpAA",
  "token_type"    : "Bearer",
  "expires_in"    : 3600,
  "scope"         : "read write"
}

The client can then use the access token to make requests to the intended protected resource (e.g. web API).

You can find more information in the OAuth 2.0 spec and in our article on using the client credentials grant.

1.2 Prerequisites

For a client to make a token request with the client credentials grant it must be registered for it.

2. Client credentials grant handler SPI

The Connect2id server comes with a flexible plugin interface (SPI) for determining the authorisation properties (scope values, etc) for a valid client credentials grant.

Features of the client credentials grant handler SPI:

  • Enables initialisation of the handler from a configuration file or system properties.

  • The handler is passed the registered client details, which can be used to determine the authorisation properties (scope values, etc).

  • Enables implementations to release resources on Connect2id server shutdown.

How does the SPI get invoked?

  1. The Connect2id server receives an access token request with a client credentials grant.

  2. The Connect2id server validates the client_id and its credentials and whether client use of the grant is permitted.

  3. On success the SPI gets invoked to determine the authorisation properties, such as scope values, access token lifetime and encoding.

3. Available implementations

The Connect2id server includes two handler implementations, released under an open source (Apache 2.0) license. You can use them as they are or as a starting point to develop your own handler implementation.

3.1 Simple handler using the registered scope values

This handler relies on the registered scope values for the client to bound the access token scope. Any requested scope values not found in the registered list will simply be left out from the authorisation.

Git repohttps://bitbucket.org/connect2id/client-credentials-grant-handler

3.1.1 Configuration

The Connect2id server is shipped with a base configuration for the simple client credentials grant handler located in WEB-INF/clientGrantHandler.properties. It can be replaced or overridden with Java system properties.

Content of the shipped handler configuration file, with explanation of the properties:

# Enables / disables the client credentials grant handler. Disabled (false) by
# default.
op.grantHandler.clientCredentials.simpleHandler.enable=true


### Access token settings ###

# The access token lifetime, in seconds.
op.grantHandler.clientCredentials.simpleHandler.accessToken.lifetime=600

# The access token encoding:
#
#     * IDENTIFIER -- The access token is a secure identifier. The associated
#       authorisation is looked up by a call to the Connect2id server token
#       introspection endpoint.
#     * SELF_CONTAINED -- Self-contained access token. The associated
#       authorisation is encoded in the access token itself, as a signed and
#       optionally encrypted JSON Web Token (JWT). Can also be looked up by a
#       call to the Connect2id server token introspection endpoint.
#
op.grantHandler.clientCredentials.simpleHandler.accessToken.encoding=SELF_CONTAINED

# If true enables additional encryption of self-contained (JWT-encoded) access
# tokens.
op.grantHandler.clientCredentials.simpleHandler.accessToken.encrypt=false

# Optional audience for the access tokens, as comma and / or space separated
# list of values.
op.grantHandler.clientCredentials.simpleHandler.accessToken.audienceList=

# Names of client metadata fields to include in the optional access token
# "data" field, empty set if none. To specify a member within a field that is a
# JSON object member use dot (.) notation.
op.grantHandler.clientCredentials.simpleHandler.accessToken.includeClientMetadataFields=

Example minimal configuration for issuing self-contained (JWT) access tokens with a lifetime of 10 minutes (600 seconds):

op.grantHandler.clientCredentials.simpleHandler.enable=true
op.grantHandler.clientCredentials.simpleHandler.accessToken.lifetime=600
op.grantHandler.clientCredentials.simpleHandler.accessToken.encoding=SELF_CONTAINED

Example configuration for issuing identifier-based access tokens which are checked at the Connect2id server token introspection endpoint:

op.grantHandler.clientCredentials.simpleHandler.enable=true
op.grantHandler.clientCredentials.simpleHandler.accessToken.lifetime=600
op.grantHandler.clientCredentials.simpleHandler.accessToken.encoding=IDENTIFIER

Example configuration which puts the registered client metadata fields software_id and data -> org_id into the access token data field:

op.grantHandler.clientCredentials.simpleHandler.enable=true
op.grantHandler.clientCredentials.simpleHandler.accessToken.lifetime=600
op.grantHandler.clientCredentials.simpleHandler.accessToken.encoding=IDENTIFIER
op.grantHandler.clientCredentials.simpleHandler.accessToken.includeClientMetadataFields=software_id,data.org_id

To verify the handler loading and configuration check the server logs for lines with the OP7106 and CGHxxxx identifiers.

Example logged configuration:

INFO MAIN [main] [OP7106] Loaded and initialized client_credentials grant handler com.nimbusds.openid.connect.provider.spi.grants.client.handler.SimpleClientCredentialsGrantHandler
INFO MAIN [main] [CCW0000] Client credentials grant handler enabled: true
INFO MAIN [main] [CCW0001] Client credentials grant handler: Endpoint URL: http://localhost:45057
INFO MAIN [main] [CCW0002] Client credentials grant handler: Accepted custom token request parameters: []
INFO MAIN [main] [CCW0005] Client credentials grant handler: Included client metadata parameters: [sector_identifier_uri, default_acr_values, subject_type, require_auth_time, data, application_type, default_max_age, scope]
INFO MAIN [main] [CCW0003] Client credentials grant handler: HTTP connect timeout: 500 ms
INFO MAIN [main] [CCW0004] Client credentials grant handler: HTTP read timeout: 500 ms

3.2 Web-based handler

This handler calls a web service to determine the authorisation properties for a client credentials grant after the Connect2id server successfully authenticated the client.

The web service interface is simple -- a single HTTP POST, using JSON to convey the request and response parameters.

Git repohttps://bitbucket.org/connect2id/grant-handlers-web

3.2.1 Configuration

The Connect2id server is shipped with a base configuration for the web-based client credentials grant handler located in WEB-INF/clientGrantHandlerWebAPI.properties.properties. It can be replaced or overridden with Java system properties.

Content of the shipped handler configuration file, with explanation of the properties:

# Enables / disables the web-based client credentials grant handler. Disabled
# (false) by default.
op.grantHandler.clientCredentials.webAPI.enable=false

# The endpoint URL of the web API.
op.grantHandler.clientCredentials.webAPI.url=

# Access token of type Bearer for the web API.
op.grantHandler.clientCredentials.webAPI.apiAccessToken=

# Names of custom token request parameters to include as top-level members in
# the handler request JSON object. The default value is none.
op.grantHandler.clientCredentials.customParams=

# Names of client metadata fields to include in the "client" JSON object which
# is part of handler request. If not specified the following client metadata
# fields are included by default: "scope", "application_type",
# "sector_identifier_uri", "subject_type", "default_max_age",
# "require_auth_time", "default_acr_values", "data".
op.grantHandler.clientCredentials.clientMetadata=

# The HTTP connect timeout, in milliseconds. The default value is zero, implies
# none or determined by the underlying HTTP client.
op.grantHandler.clientCredentials.webAPI.connectTimeout=250

# The HTTP response read timeout, in milliseconds. The default value is zero,
# implies none or determined by the underlying HTTP client.
op.grantHandler.clientCredentials.webAPI.readTimeout=500

To set up the handler the following minimal configuration must be provided:

  • Enabling of the handler.
  • The URL of the web service.
  • A long-lived bearer token to access the web service.

Example minimal configuration:

op.grantHandler.clientCredentials.webAPI.enable=true
op.grantHandler.clientCredentials.webAPI.url=https://login.example.com/client-credentials-grant-handler
op.grantHandler.clientCredentials.webAPI.apiAccessToken=ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6

To verify the handler loading and configuration check the server logs for lines with the OP7106 and CCWxxxx identifiers.

Example logged configuration:

INFO MAIN [main] [OP7106] Loaded and initialized client_credentials grant handler com.nimbusds.openid.connect.provider.spi.grants.handlers.web.clientcredentials.ClientCredentialsGrantDelegator
INFO MAIN [main] [CCW0000] Client credentials grant handler enabled: true
INFO MAIN [main] [CCW0001] Client credentials grant handler: Endpoint URL: http://localhost:45057
INFO MAIN [main] [CCW0002] Client credentials grant handler: Accepted custom token request parameters: []
INFO MAIN [main] [CCW0005] Client credentials grant handler: Included client metadata parameters: [sector_identifier_uri, default_acr_values, subject_type, require_auth_time, data, application_type, default_max_age, scope]
INFO MAIN [main] [CCW0003] Client credentials grant handler: HTTP connect timeout: 500 ms
INFO MAIN [main] [CCW0004] Client credentials grant handler: HTTP read timeout: 500 ms

3.2.2 Web API

For each access token request with a client credentials grant the Connect2id server will first authenticate the client and whether the client registration permits use of the grant. Only then will the server call the SPI (the connector to the web service) to process the grant.

The web service is expected to perform the following:

  • Determine the authorisation scope. The logic for that can use the requested scope (if any), the registered client ID and metadata as inputs.

The web service interface is specified as follows:

HTTP POST

Header parameters:

  • Authorization The configured bearer access token for the web service (see op.grantHandler.clientCredentials.webAPI.apiAccessToken).

  • Content-Type Set to application/json.

  • Issuer The issuer URL. Since v12.16.

Body:

  • A JSON object with the following members:

    • [ scope ] {string array} The requested scope values, as specified in the access token request, empty array or omitted if none.

    • [ resources ] {string array} The requested target resource URI(s), omitted if none. Since v12.16.

    • client {object} JSON object containing the client_id and selected client metadata according to the op.grantHandler.clientCredentials.webAPI.clientMetadata configuration.

    • [ ... ] {string} Other custom token request parameters according to the op.grantHandler.clientCredentials.webAPI.customParams configuration. Since v12.16.

Success:

  • Code: 200

  • Content-Type: application/json

  • Body: {object} A JSON with object with the following authorisation properties:

    • scope {string array} An array of one or more authorised scope values for the access token. May be different from the originally requested scope values.

    • [ audience ] {string array} Optional explicit list of audiences for the access token, omitted if none. Will be deprecated, use the access token property below!

    • [ access_token ] {object} Optional access token settings:

      • [ lifetime = 0 ] {integer} The access token lifetime in seconds. If zero or omitted defaults to the configured access token lifetime.
      • [ encoding = "SELF_CONTAINED" ] {"SELF_CONTAINED"|"IDENTIFIER"} The access token encoding. If omitted defaults to self-contained (JWT-encoded).
      • [ audience ] {string array} Optional explicit list of audiences for the access token, omitted if none.
      • [ encrypt = false ] {true|false} Encryption flag. Applies only to self-contained (JWT-encoded) access tokens. If true the JWT will be encrypted with a shared AES key after signing for confidentiality.
    • [ claims ] {string array} Optional array of names of consented OpenID claims.

      Special keywords and prefixes:

      * - An asterisk ("*") in the string array indicates consent for all requested claims.

      id_token: - For a non-requested claim, the "id_token:" prefix to its name will cause the claim to be delivered in the issued ID token instead of at the UserInfo endpoint (default location for all non-requested claims).

      access_token: - For a non-requested claim, this prefix will cause the claim to be delivered in the issued access token. If the access token is self-contained (JWT) the claim will be added at the top-level. If the access token is identifier-based the claim will appear at the top-level in the token introspection response.

      access_token:uip: - For a non-requested claim, this prefix will cause the claim to be merged into the top-level "uip" (optional preset UserInfo claims) JSON object claim of the access token.

      access_token:dat: - For a non-requested claim, this prefix will cause the claim to be merged into the top-level "dat" (optional data) JSON object claim of the access token.

      verified: - If Identity Assurance is enabled indicates a verified claim. Must be after any other prefixes.

    • [ claims_locales ] {string array} Optional array of the claims locales, omitted if not specified.

    • [ claims_data ] {object} Optional data to be passed in the request to retrieve the consented OpenID claims from the configured source(s). The claims data will be included in a "cld" (claims data) field in the issued access token(s) and in the long-lived authorisations if the consent is persisted. If the claims data must be kept confidential from the client either an identifier access token encoding must be chosen or if a self-contained (JWT) access token is chosen it must be additionally encrypted. An AdvancedClaimsSource SPI implementation can retrieve the claims data JSON object by a call to the ClaimsSourceRequestContext.getClaimsData method.

    • [ data ] {object} Optional additional information to be stored in the dat field of the authorisation record and self-contained (JWT-encoded) access tokens.

Errors:

Example request from a client with ID 123:

POST /client-credentials-grant-handler HTTP/1.1
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/json
Issuer: https://c2id.com

{
  "scope"    : [ "read", "write" ],
  "client"   : { "client_id"        : "123",
                 "application_type" : "web" }
}

Example response for a successful authorisation with a read scope:

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

{
  "scope" : [ "read" ]
}

Example response indicating an invalid scope error:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error"             : "invalid_scope",
  "error_description" : "Invalid / illegal scope"
}

4. How to develop your own client credentials grant handler

First, read our general guide for developing, annotating and packaging an SPI-based plugin.

The connector must implement the ClientCredentialsGrantHandler SPI defined in the Connect2id server SDK:

Git repohttps://bitbucket.org/connect2id/server-sdk

If the Connect2id server detects an SPI implementation at startup it will log its loading under OP7106:

INFO MAIN [main] [OP7106] Loaded and initialized client_credentials grant handler com.nimbusds.openid.connect.provider.spi.grants.handlers.web.clientcredentials.ClientCredentialsGrantDelegator

Note, the Connect2id server can load multiple client credentials grant handlers at startup, but only one may be enabled at a time.