Client Credentials Flow
For services that call other services, with no user involved.
- Who's involved
- Your service, auth server, API
- Best for
- Server-to-server calls, no user
- Difficulty
- Easy
1Overview
How it works
This flow is for machine-to-machine (M2M) authentication. Your service proves who it is with its client_id and client_secret, and receives an access token it can use to call other APIs. There are no redirects and no sign-in screens.
It suits:
- backend services and microservices that talk to each other
- scheduled jobs and daemons (long-running background programs)
- command-line tools
If you need access on behalf of a user, use Authorization Code or PKCE instead.
2Use cases
When would you use this?
A scheduled job
A cron job at night calls an internal reporting API to pull yesterday's numbers.
Service to service
A billing service calls the inventory service. No person is involved, only the two programs.
A backend integration
Your server calls a partner's API to sync products, using its own credentials.
Not the right fit? Acting for a user? This flow has no user. Use Authorization Code (or PKCE) so the token is tied to a person.
3Interactive
Try it step by step
See it step by step
Step 1 of 4
The service has its own ID and secret
These work like a username and password for a program. Keep them out of the code.
4Practical advice
Do this, avoid that
Good practices
- Cache tokens until they expire โ Do not request a new token for every API call. It wastes time and may hit rate limits
- Store credentials securely โ Use environment variables or a secrets manager such as AWS Secrets Manager or HashiCorp Vault
- Request only the
scopevalues you need โ A scope is a permission. Ask only for what your service actually uses - Rotate credentials regularly โ Replace secrets on a schedule, so a leaked one stops working
- Handle expiry gracefully โ If a request fails, retry with increasing waits between attempts (exponential backoff)
Common mistakes
- Using this flow when a user is involved โ If you need a user's permission, use the Authorization Code flow instead
- Putting
client_secretin frontend code โ This flow is for backend services only - Hardcoding credentials โ Never commit secrets to version control
- Requesting a new token for every request โ Cache and reuse tokens until they expire
- Ignoring token expiry โ Check
expires_inand get a new token before the old one runs out
5Code
Code example
Show the code
// Client Credentials Flow Implementation
// This code runs on your backend server/service ONLY
async function getAccessToken() {
const tokenUrl = 'https://auth-server.com/oauth/token';
// Prepare credentials
const credentials = {
grant_type: 'client_credentials',
client_id: 'your-service-client-id',
client_secret: 'your-service-client-secret',
scope: 'api.read api.write' // Request specific scopes
};
try {
const response = await fetch(tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
// Some servers prefer Basic Auth:
// 'Authorization': `Basic ${Buffer.from(`${client_id}:${client_secret}`).toString('base64')}`
},
body: new URLSearchParams(credentials)
});
if (!response.ok) {
throw new Error(`Token request failed: ${response.status}`);
}
const tokens = await response.json();
// tokens = { access_token, token_type, expires_in, scope }
return tokens;
} catch (error) {
console.error('Failed to obtain access token:', error);
throw error;
}
}
// Use the token to access protected resources
async function callProtectedAPI(accessToken) {
const response = await fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`);
}
return response.json();
}
// Example: Token caching for efficiency
class TokenManager {
constructor() {
this.token = null;
this.expiresAt = null;
}
async getValidToken() {
// Check if we have a valid cached token
if (this.token && this.expiresAt && Date.now() < this.expiresAt) {
return this.token;
}
// Fetch new token
const tokenData = await getAccessToken();
this.token = tokenData.access_token;
// Set expiration with 5 minute buffer
const expiresIn = tokenData.expires_in || 3600;
this.expiresAt = Date.now() + (expiresIn - 300) * 1000;
return this.token;
}
}
// Usage example
const tokenManager = new TokenManager();
async function performBackgroundJob() {
try {
const token = await tokenManager.getValidToken();
const data = await callProtectedAPI(token);
console.log('Job completed:', data);
} catch (error) {
console.error('Job failed:', error);
}
}