Skip to main content

Client Credentials Grant

Use the client credentials grant for machine-to-machine flows where no user is involved — whichever party is authenticating proves its own identity directly and gets a token for itself, with no resource owner in the picture. RFC 6749's "client" is a generic role, not a synonym for O2ID's Application resource — several different principals can authenticate this way; see Getting a Token as an Unattended Agent for how an AI agent uses this same grant with a signed assertion instead of a client secret, and Getting a Token for a Resource Server for how to scope the resulting token to a specific downstream API rather than O2ID's own management API. Only confidential applications (those registered with a clientSecret) can use the application variant below.

Token endpoint: POST /oauth2/token

ParameterRequiredDescription
grant_typeYesMust be client_credentials
client_idYesThe application's client ID
client_secretYesThe application's client secret
scopeNoSpace-delimited list of requested scopes
# Create a confidential application
APP_JSON=$(curl -s -X POST http://localhost:8080/applications \
-H "Content-Type: application/json" \
-d '{"name":"Service App","clientSecret":"my-secret","callbackUrls":["https://app.example.com/callback"]}')

CLIENT_ID=$(printf '%s' "$APP_JSON" | jq -r .clientId)

# Request a token
curl -s -X POST http://localhost:8080/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=${CLIENT_ID}" \
--data-urlencode "client_secret=my-secret" \
--data-urlencode "scope=read write"

The response includes access_token and token_type but no refresh_token (RFC 6749 §4.4.3). The sub claim in the token is the client's own client ID. Public clients (registered without a clientSecret) receive {"error":"unauthorized_client"}. The granted scope is clamped to the application's own allowedScopes (see Managing Applications) — there is no human role scope involved in this path at all, since client_credentials has no concept of a resource owner.