Client Credentials
The Client Credentials grant (RFC 6749 §4.4) is the machine-to-machine flow. A backend service authenticates with its own credentials at the token endpoint and receives an access token that represents the service itself, with no user context, no redirect, and no browser.
Use it for service-to-service calls, internal daemons, scheduled jobs, and CLI tools that act under a system identity rather than on behalf of a user.
How It Works
How ThunderID Implements It
| Aspect | Behavior |
|---|---|
| Endpoint | POST /oauth2/token |
| Grant type | client_credentials |
| Client type | Intended for confidential clients that authenticate with their own credentials |
| Token subject | sub in the issued access token is the client_id |
| End-user attributes | Not included: there is no authenticated user |
| Client attributes | The client's own attributes can be added as subject claims via clientConfig.attributes. OU claims (ouId, ouName, ouHandle), groups, and roles are available to any client; agents can also include their schema attributes and system attributes (name, owner). All are opt-in and must be listed explicitly. |
| Subject class | sub_type carries application or agent, so a resource server can apply policy that differs by identity class. Selected through clientConfig.attributes, and added there when the client is created, so a new client carries it without configuration. Clear the sub_type chip on the Console Token tab to drop it. Its value always comes from the server's record of the client's class |
| Refresh token | Not issued for this grant (re-request when needed) |
| Scope filtering | Requested scopes are filtered to permissions defined on the target resource server, then intersected with the permissions the client holds through its role and group assignments |
aud claim | The supplied resource server identifier, or defaultResourceServer for a permission-bearing request without resource. A scopeless request without resource uses the application's token.accessToken.defaultAudience, falling back to client_id |
| ID token | Not issued: this is an OAuth-only flow, not OIDC |
OU claims are opt-in: a client receives only the claims it lists in clientConfig.attributes. Agents configure these on the Token tab in the Console. Applications can set clientConfig.attributes through the API when needed.
sub_type is added to clientConfig.attributes when a client is created, so a new client carries the claim without configuration. This covers the Console, the REST API, dynamic client registration, and resources imported with target: runtime, all of which create the client through the same service.
A client that is only ever read from a declarative file, that is, one served by an application or agent store running in declarative mode, is never created through that path, so it has to list the claim itself:
resource_type: application
name: Reporting Service
type: m2m
ouHandle: default
inboundAuthConfig:
- type: oauth2
config:
grantTypes:
- client_credentials
token:
accessToken:
clientConfig:
attributes:
- sub_type
Leaving it out is a valid choice, not an error: the client simply receives no sub_type. The same applies to a client created before the claim existed, which carries it once the chip is selected on the Token tab. A resource server should therefore treat a missing sub_type as an unknown identity class rather than assuming one.
Try It in ThunderID
- Console
- Dynamic Client Registration
- Open Applications or Agents in the ThunderID Console and select your client.
- Open the Advanced tab.
- Under Grant Types, select
client_credentials. - Choose a confidential Token Endpoint Authentication method (see Client Authentication Methods).
- Assign the roles or groups that grant the permissions the service should hold: these determine the scopes it can be issued.
- Save.
When requesting permission scopes, the client must include resource unless defaultResourceServer is configured globally. A scopeless request does not require either setting and uses the application's token.accessToken.defaultAudience (falling back to client_id) as its audience.
POST /oauth2/dcr/register
Content-Type: application/json
{
"client_name": "Billing Service",
"grant_types": ["client_credentials"],
"token_endpoint_auth_method": "client_secret_basic",
"scope": "reservations:create reservations:view"
}
Request a Token
curl -X POST https://thunderid.example.com/oauth2/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d "grant_type=client_credentials" \
-d "scope=reservations:create" \
-d "resource=https://api.example.com/booking"
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiI...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "reservations:create"
}
Related Guides
- Client Authentication Methods, required for confidential clients
- Resource Indicators, target the token at a specific resource server
- Token Exchange, exchange a service token for a downscoped or audience-restricted one
- Token Introspection, let resource servers validate the token