Safeguard
Security Guides•

OAuth client credentials grant for service to service auth

How the client credentials grant works for machine to machine calls, where the secret should live, and the mistakes that turn it into a shared password.

Safeguard Security Team
3 min read

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

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.