Client Credentials

Machine-to-machine authorization: with no user involved, the app authenticates as itself to obtain an access token, then calls a protected API with it. Used for service-to-service calls, daemons, and cron jobs.

Client App
Authorization Server
Resource Server
Back channel (server-to-server)
Step 1 / 4Client AppAuthorization Server

POST /token

The client requests a token directly from the token endpoint. There is no /authorize step and no browser: the client authenticates as itself with HTTP Basic (client_id:client_secret) and asks for the scopes it needs. grant_type=client_credentials is what selects this grant.

Payload
POST https://auth.example.com/token
Headers
Content-Typeapplication/x-www-form-urlencoded
AuthorizationBasic aW52ZW50b3J5LXN2Yy1wcm9kOjhmMmEtQ3Yza1JxN0xwMVp4OU53MFl0Ng==
Body
grant_type=client_credentials&scope=inventory%3Aread
Authorization: Basic
base64(client_id:client_secret). The client proves it is itself — there is no user to authenticate in this grant.
grant_type=client_credentials
Selects the Client Credentials grant (RFC 6749 §4.4). No authorization code, no redirect, no user consent.
scope=inventory:read
Requests only the access this service needs. The server may grant fewer scopes than asked.

What problem does this solve?

Sometimes there is no user to delegate from. A backend service, a cron job, or a daemon needs to call an API as itself — service-to-service. The Client Credentials grant (RFC 6749 §4.4) covers exactly this: the application authenticates with its own credentials (client_id / client_secret) and receives a scoped access token for the API. No browser, no redirect, no login screen, no consent.

Because the client is the only party here, every hop is a back channel (direct server-to-server). There is no front channel to protect — which also means this grant is only appropriate for confidential clients that can actually keep a secret. A browser SPA or mobile app cannot, and must not, use it.

Walking the flow

  • POST /token — the client hits the token endpoint directly. It authenticates with HTTP Basic (Authorization: Basic base64(client_id:client_secret)) and sets grant_type=client_credentials, requesting just scope=inventory:read. There is no authorization code to redeem and no user to prompt.
  • 200 Access token — the server validates the client and returns a Bearer token, its expires_in lifetime, and the granted scope. Notice what's missing: no refresh_token. §4.4.3 says this grant MUST NOT issue one — when the token expires, the client simply requests another with the same call.
  • GET /api/inventory — the client calls the resource server with Authorization: Bearer <token>.
  • 200 Protected data — the resource server validates the token and its scope, then returns the inventory JSON. The data is service-level, not user-specific.

Security notes

  • Protect the client_secret. It is the only thing standing between an attacker and a valid token. Store it in a secrets manager, rotate it, and never ship it to a public client (browser/mobile). Prefer private_key_jwt / mTLS client authentication over a shared secret where the authorization server supports it.
  • Request least privilege. Ask only for the scopes the service needs (inventory:read), and have the resource server enforce them.
  • Always use TLS. A Bearer token is a password-equivalent; anyone who captures it can replay it until it expires. Keep expires_in short.
  • Don't reach for a refresh token. Its absence is by design — re-running the token request is the intended renewal path.
  • This is authorization, not authentication of a person. There is no user identity in the token; never treat a client-credentials token as "who is logged in." For user identity, use OpenID Connect.

Further reading