The client credentials grant lets a backend service get an access token as itself, with no user involved. The service authenticates to the authorization server, asks for a scope, and receives a token to call another API. It is meant for confidential clients only, and the secret is the weak point.
How it works
The client sends a POST to the token endpoint with grant_type=client_credentials and, optionally, a scope. It must authenticate, commonly with a client ID and secret, and the server authenticates it in return. The server answers with an access token. RFC 6749 says the response should not include a refresh token, since the client can get a new token whenever it needs one.
POST /token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=reports.read
When it fits
- A nightly job calling an internal reporting API.
- One microservice calling another, where the caller's own identity matters.
- A CI job calling a deployment API.
It does not fit when the action is on behalf of a particular person. The token represents the service, so audit logs will show the service, not the user. If you need the person's identity in the chain, see token exchange.
Confidential clients only
RFC 6749 states that this grant MUST only be used by confidential clients, meaning clients that can keep credentials secret. A mobile app, a browser app, or a desktop tool distributed to users cannot. Anything shipped to a user's device should be assumed readable.
Where things go wrong
One shared secret for everything. If ten services share a client, you cannot tell them apart in logs or revoke one without breaking all. Give each workload its own client.
Secrets in code and config. Store them in a secrets manager and inject at runtime. Rotate on a schedule and after any staff change that touched them.
Broad scopes. Request the narrowest scope per call. A token that can do everything is what an attacker wants.
Long lived tokens. Short expiry limits the damage of a leak. Since no refresh token is issued, a short lifetime costs you only another token request.
No audience. The API should reject tokens not meant for it. RFC 9700 says access tokens should be restricted to a specific resource server and resource servers must refuse tokens issued for others.
Stronger options than a shared secret
Instead of a static secret, clients can authenticate with a signed assertion or a certificate, so there is no long lived password to leak. In cloud and cluster environments, workload identity removes stored secrets altogether. See SPIFFE IDs and SVIDs for one approach.
Quick check
Should a client credentials flow return a refresh token?
RFC 6749 says it should not. The client can authenticate again to get a new access token.
Can I use it from a mobile app?
No. A mobile app is a public client and cannot protect a secret. Use authorization code with PKCE.
Sources
- RFC 6749, The OAuth 2.0 Authorization Framework
- RFC 9700, Best Current Practice for OAuth 2.0 Security
Last reviewed: 19 October 2026.