Client credentials grant handler SPI

1. Introduction

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

The handler comes with a simple configuration stored in the clientGrantHandler.properties file in the WEB-INF directory of the Connect2id server.

### ### OAuth 2.0 client credentials grant handler configuration ### ###

# This file configures the local handler for client credentials grants.
#
# See:
#
# 1. https://bitbucket.org/connect2id/client-credentials-grant-handler
# 2. https://bitbucket.org/connect2id/server-sdk
# 3. https://connect2id.com/products/server/docs/integration/client-credentials-grant-handler

# 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

Any configuration file property can be overridden by setting a Java system property with the same key, e.g. by using the optional -D argument at JVM startup:

-Dop.grantHandler.clientCredentials.simpleHandler.enable=false

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/client-credentials-grant-web-api

3.2.1 Configuration

To set up a web handler for processing client credentials grants you need to provide the following configuration details:

  • The URL of the web service for establishing the authorisation scope.

  • A long-lived bearer access token for the web service.

  • HTTP connect and read timeouts.

Example configuration:

op.grantHandler.clientCredentials.webAPI.enable=true
op.grantHandler.clientCredentials.webAPI.url=https://c2id.com/client-credentials-grant-handler
op.grantHandler.clientCredentials.webAPI.apiAccessToken=ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
op.grantHandler.clientCredentials.webAPI.connectTimeout=250
op.grantHandler.clientCredentials.webAPI.readTimeout=500

If a file-based configuration is used it must be stored at WEB-INF/clientGrantHandlerWebAPI.properties.

The configuration can be alternatively passed as Java system properties, which will override any file-based ones. Java system properties can be passed using the optional -D argument at JVM startup:

-Dop.grantHandler.clientCredentials.webAPI.enable=true

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 Will be set to application/json.

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.
    • client {object} JSON object containing the client_id and registered metadata for the client.

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.
    • [ access_token ] {object} Optional access token settings, overriding the default configuration:
      • [ 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).
      • [ 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.
    • [ 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 000123:

POST /client-credentials-grant-handler HTTP/1.1
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/json

{
  "scope"    : [ "read", "write" ],
  "client"   : { "client_id"                  : "000123",
                 "client_name"                : "My Test App",
                 "grant_types"                : [ "client_credentials" ],
                 "response_types"             : [],
                 "token_endpoint_auth_method" : "client_secret_basic",
                 "application_type"           : "web" }
}

Example response for a successful authorisation:

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.client.handler.SimpleClientCredentialsGrantHandler

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