โ† All flows

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

โš™๏ธ
Service
๐Ÿ”
Login server
๐Ÿ“ฆ
API
ID + secret

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 scope values 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_secret in 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_in and get a new token before the old one runs out

5Code

Code example

Show the code
Client Credentials Flow example
javascript
// 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);
  }
}