HTTP APIs

larsggu.meReference › OAuth 2.0 client credentials

OAuth 2.0 client credentials

POST /token grant_type=client_credentials -> access_token

The OAuth 2.0 flow for machine-to-machine access, in which the application authenticates as itself with no user present and no user consent involved.

Description

Not every integration acts for a person. A nightly reconciliation job, a data export, a service that watches a queue: these act for an organisation, and there is no browser to redirect and nobody to click consent. The client credentials grant covers that case. The application presents its own credential directly to the token endpoint and receives an access token scoped to itself.

Because there is no user in the exchange, there is also no user whose permissions bound the token. The scopes attached to the client are the only limit, which makes the registration of that client the security decision. A client registered with every scope available is a standing key to the whole account, and it is the flow's characteristic failure.

Tokens from this grant are short-lived and are not accompanied by a refresh token, since the application can simply authenticate again. That makes caching the token until shortly before expiry the expected behaviour: requesting a new token per API call is a common and avoidable source of rate-limit pressure on the token endpoint.

Client authentication itself may be a shared secret or an assertion signed with a private key. The assertion form is preferable where it is available, because the secret never leaves the client and a captured request cannot be replayed to obtain a new token.

Fields

Fields of OAuth 2.0 client credentials
FieldFormMeaning
grant_typeclient_credentialsNames the flow.
client_idstringIdentifies the client.
client_secretstringShared-secret client authentication, sent over the back channel only.
client_assertionsigned JWTAsymmetric alternative to the shared secret.
scopespace-delimited listBounded by what the client registration allows.
expires_insecondsToken lifetime. Cache until shortly before this elapses.

Example

Obtaining and reusing a token

POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <client_id:client_secret>

grant_type=client_credentials&scope=ledger.read+ledger.export

HTTP/1.1 200 OK
{"access_token":"at_...","token_type":"Bearer","expires_in":3600,
 "scope":"ledger.read ledger.export"}

GET /v1/ledger/entries HTTP/1.1
Authorization: Bearer at_...

One token is fetched, then reused for every request until it approaches expiry.

Failure modes

  • Requesting a token per request instead of caching it, which loads the token endpoint far more than the API itself.
  • Registering the client with the full scope list because narrowing it later seems harder.
  • Holding the client secret in a build artefact rather than in the environment, where it travels wherever the artefact travels.
  • Treating expiry as an error to alert on rather than a routine refresh.

Topic: Identity. Last modified 2026-09-06.