Skip to main content

Client Credentials Flow

The Client Credentials Flow is used for machine-to-machine (M2M) communication, where no user is involved. This flow is ideal for backend services, cron jobs, and microservices.

When to use it​

  • A backend service accessing an API
  • Scheduled tasks / cron jobs
  • Microservice-to-microservice communication
  • Any scenario without user interaction

Limitations​

  • No user context: Tokens represent the application, not a user
  • No refresh tokens: When a token expires, you must request a new one
  • No ID token: User identity claims are not available

Token request​

curl -X POST https://your-sso-domain.com/connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=my-backend-service" \
-d "client_secret=my-service-secret" \
-d "scope=api"

Parameters:

ParameterRequiredDescription
grant_typeYesMust be client_credentials
client_idYesYour application's client ID
client_secretYesYour application's client secret
scopeNoThe requested scope for API access (api). User scopes (openid, profile, …) cannot be used here.

Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9...",
"token_type": "Bearer",
"expires_in": 1800,
"scope": "api"
}

Claims in the token​

Client credentials tokens contain the application's identity:

{
"sub": "my-backend-service",
"name": "My Backend Service",
"scope": "api",
"exp": 1704067200,
"iss": "https://your-sso-domain.com/"
}

The sub matches the application's client_id, and name is its display name. A client credentials token contains no user claims (e.g. email) and no role.

Best practices​

  1. Cache tokens: Reuse tokens until they expire (check expires_in)
  2. Request minimal scopes: Request only the scopes you actually need
  3. Secure your credentials: Store the client_secret in environment variables or a secret manager
  4. Handle token expiration: Request a new token when the current one expires
// Example: Token caching logic
let cachedToken = null;
let tokenExpiry = null;

async function getAccessToken() {
// Return the cached token if it's still valid (with a 60s buffer)
if (cachedToken && tokenExpiry > Date.now() + 60000) {
return cachedToken;
}

// Request a new token
const response = await fetch('https://your-sso-domain.com/connect/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'grant_type=client_credentials&client_id=...&client_secret=...'
});

const data = await response.json();
cachedToken = data.access_token;
tokenExpiry = Date.now() + (data.expires_in * 1000);

return cachedToken;
}