Skip to content

Client Credentials Grant

The Client Credentials grant allows a confidential client to obtain an access token on its own behalf, without any user involvement. This is designed for machine-to-machine (M2M) communication where the client itself is the resource owner.

Terminal window
curl -X POST https://auth.example.com/oauth2/token \
-u "my-service:my-service-secret" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "scope=read write"

Or with client_secret_post authentication:

Terminal window
curl -X POST https://auth.example.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=my-service" \
-d "client_secret=my-service-secret" \
-d "scope=read write"
Parameter Required Description
grant_type Yes Must be client_credentials
scope No Space-separated list of scopes (defaults to client’s allowed scopes, minus openid)
client_id Conditional Required if not using HTTP Basic authentication
client_secret Conditional Required if not using HTTP Basic authentication

Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 900,
"scope": "read write"
}

Note the absence of id_token and refresh_token in the response. Per RFC 6749 Section 4.4.3, a refresh token SHOULD NOT be included.

Only confidential clients can use the client credentials grant. Public clients will receive an unauthorized_client error. This is enforced per RFC 6749 Section 4.4.2.

The client MUST authenticate with the authorization server. Supported authentication methods:

  • client_secret_basic – client ID and secret sent via HTTP Basic authentication header
  • client_secret_post – client ID and secret sent in the request body

The client must have client_credentials in its grant_types:

Terminal window
curl -X POST https://auth.example.com/admin/api/clients \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client_id": "my-service",
"client_name": "My Background Service",
"client_secret": "my-service-secret",
"redirect_uris": [],
"grant_types": ["client_credentials"],
"response_types": [],
"scopes": "read write",
"client_type": "confidential",
"token_endpoint_auth_method": "client_secret_basic"
}'
  • If no scope is provided, the client’s configured scopes are used
  • The openid scope is automatically stripped because there is no user identity to assert
  • If no valid scopes remain after stripping openid, the request fails with invalid_scope
  • Requested scopes are validated against the client’s allowed scopes

The access token is a signed JWT (RS256) with the following notable claims:

Claim Value
sub The client_id (the client is the resource owner)
azp The client_id
aud Includes the issuer URL and any configured allowed_audiences
scope The granted scopes
  • No user identity – no id_token is issued
  • No refresh token – each token must be obtained independently
  • No SSO session – each grant is a standalone token issuance
  • Confidential clients only – public clients cannot use this grant