Client credentials grant handler
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?
-
The Connect2id server receives an access token request with a client credentials grant.
-
The Connect2id server validates the client_id and its credentials and whether client use of the grant is permitted.
-
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 repo | https://bitbucket.org/connect2id/grant-handlers |
---|
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 grant handler. Disabled (false) by default.
op.grantHandler.clientCredentials.simpleHandler.enable=true
### Access token ###
# The access token lifetime, in seconds. If zero, blank or omitted the default
# access token lifetime configured by "authzStore.accessToken.defaultLifetime"
# applies.
op.grantHandler.clientCredentials.simpleHandler.accessToken.lifetime=
# The access token encoding. The default value is SELF_CONTAINED.
#
# Supported encodings:
#
# * 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. Disabled (false) by default.
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 repo | https://bitbucket.org/connect2id/grant-handlers |
---|
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 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.
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.
-
client {object} JSON object containing the
client_id
and selected client metadata according to theop.grantHandler.clientCredentials.webAPI.clientMetadata
configuration. -
[ … ] {string} Other custom token request parameters according to the
op.grantHandler.clientCredentials.webAPI.customParams
configuration.
-
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:
-
400 Bad Request
- With an encoded
invalid_grant
error indicates invalid client credentials. See OAuth 2.0, section 5.2 on error responses for details. - With an encoded
invalid_scope
error indicates the requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner. See OAuth 2.0, section 5.2 on error responses for details. - Any other bad request.
- The error JSON object can use a custom
error
code and include additional members other thanerror_description
anderror_uri
. These will be conveyed verbatim in the token error response.
- With an encoded
-
401 Unauthorized – With an encoded bearer token error indicates the access token is invalid. See OAuth 2.0 Bearer Token, section 3.1 on error responses for details.
-
500 Internal Server Error – Indicates an internal grant processing error.
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 repo | https://bitbucket.org/connect2id/server-sdk |
---|
The handler conveys an error by throwing a
GeneralException.
The exception message is logged internally and not
conveyed to the client. The
ErrorObject
determines the HTTP status code, the error
code and any other additional or
custom parameters for the token error response.
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.