OAuth 2.0 Client Credentials Flow
Complete guide to implementing server-to-server authentication with OAuth 2.0
Introduction
The OAuth 2.0 Client Credentials flow is designed for machine-to-machine (M2M) authentication where a client application needs to access protected resources on its own behalf, not on behalf of a user. This flow is ideal for backend services, microservices, CLI tools, and automated processes that need to authenticate with APIs.
Unlike the Authorization Code flow which involves user interaction, Client Credentials is a direct exchange between the client and the authorization server. The client authenticates using its own credentials (client ID and client secret) and receives an access token that can be used to call protected APIs.
Key Benefits
- • Simple Implementation: No user interaction or redirects required
- • Server-to-Server: Perfect for backend services and microservices
- • Secure: Client credentials never exposed to end users
- • Scalable: Supports high-volume API access patterns
- • Standardized: Industry-standard OAuth 2.0 protocol
When to Use Client Credentials
Use Client Credentials flow when:
- • Your application is a backend service or daemon
- • You need server-to-server authentication
- • The client is confidential and can securely store credentials
- • No user interaction is required
- • You're accessing resources owned by the application itself
How Client Credentials Flow Works
The Client Credentials flow is the simplest OAuth 2.0 flow, consisting of just two steps: the client authenticates with the authorization server and receives an access token. Here's the complete sequence:
Sequence Diagram
This diagram shows the complete authentication flow between the client application and the authorization server:
OAuth 2.0 Client Credentials Flow
Complete sequence diagram showing the authentication process
Rendering diagram...
Step-by-Step Breakdown
Step 1: Client Requests Access Token
The client application sends a POST request to the authorization server's token endpoint with its credentials:
- •
grant_type: Must be "client_credentials" - •
client_id: The client's unique identifier - •
client_secret: The client's secret key - •
scope: (Optional) Requested permissions
Step 2: Authorization Server Validates and Issues Token
The authorization server validates the client credentials and returns an access token:
- •
access_token: JWT or opaque token for API access - •
token_type: Usually "Bearer" - •
expires_in: Token lifetime in seconds - •
scope: Granted permissions
Step 3: Client Uses Access Token
The client includes the access token in the Authorization header when calling protected APIs. When the token expires, the client requests a new one using the same credentials.
Implementation Examples
Here are practical examples of implementing the Client Credentials flow in different programming languages and frameworks.
OAuth 2.0 Client Credentials Implementation
Complete implementation examples with automatic token management and error handling
import requests
import time
from typing import Optional
class OAuth2Client:
def __init__(self, token_url: str, client_id: str, client_secret: str):
self.token_url = token_url
self.client_id = client_id
self.client_secret = client_secret
self.access_token: Optional[str] = None
self.token_expires_at: float = 0
def get_access_token(self) -> str:
"""Get a valid access token, refreshing if necessary."""
if self.access_token and time.time() < self.token_expires_at:
return self.access_token
# Request new token
response = requests.post(
self.token_url,
data={
'grant_type': 'client_credentials',
'client_id': self.client_id,
'client_secret': self.client_secret,
'scope': 'read write'
},
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
response.raise_for_status()
token_data = response.json()
self.access_token = token_data['access_token']
# Set expiry with 60 second buffer
self.token_expires_at = time.time() + token_data['expires_in'] - 60
return self.access_token
def call_api(self, api_url: str, method: str = 'GET', **kwargs) -> dict:
"""Make an authenticated API call."""
token = self.get_access_token()
headers = kwargs.pop('headers', {})
headers['Authorization'] = f'Bearer {token}'
response = requests.request(method, api_url, headers=headers, **kwargs)
response.raise_for_status()
return response.json()
# Usage
client = OAuth2Client(
token_url='https://auth.example.com/oauth/token',
client_id='your_client_id',
client_secret='your_client_secret'
)
# Make API calls - token management is automatic
data = client.call_api('https://api.example.com/resources')
print(data)Security Best Practices
The Client Credentials flow involves sensitive credentials that must be protected. Follow these security best practices to ensure your implementation is secure.
1. Secure Credential Storage
Never hardcode client credentials in your source code or commit them to version control.
# ❌ BAD: Hardcoded credentials
client_id = "abc123"
client_secret = "secret_key_xyz"
# ✅ GOOD: Use environment variables
import os
client_id = os.environ['OAUTH_CLIENT_ID']
client_secret = os.environ['OAUTH_CLIENT_SECRET']
# ✅ BETTER: Use a secrets manager
from aws_secretsmanager import get_secret
credentials = get_secret('oauth/client-credentials')
client_id = credentials['client_id']
client_secret = credentials['client_secret']2. Use HTTPS Only
Always use HTTPS for token requests and API calls. Never send credentials or tokens over unencrypted connections.
# ❌ BAD: HTTP connection
token_url = 'http://auth.example.com/oauth/token'
# ✅ GOOD: HTTPS connection
token_url = 'https://auth.example.com/oauth/token'
# ✅ BETTER: Enforce HTTPS and verify certificates
import requests
response = requests.post(
'https://auth.example.com/oauth/token',
verify=True, # Verify SSL certificate
timeout=10 # Set reasonable timeout
)3. Implement Token Caching
Cache access tokens and reuse them until they expire. This reduces load on the authorization server and improves performance.
class TokenCache:
def __init__(self):
self.token = None
self.expires_at = 0
def get_token(self, fetch_fn):
"""Get cached token or fetch new one."""
if self.token and time.time() < self.expires_at:
return self.token
# Fetch new token
token_data = fetch_fn()
self.token = token_data['access_token']
# Add 60 second buffer before expiry
self.expires_at = time.time() + token_data['expires_in'] - 60
return self.token4. Use Minimal Scopes
Request only the scopes (permissions) your application needs. This follows the principle of least privilege.
# ❌ BAD: Requesting all scopes
scope = 'read write delete admin'
# ✅ GOOD: Request only what you need
scope = 'read' # Only need to read data
# ✅ BETTER: Be specific about resources
scope = 'read:users read:projects' # Specific resource access5. Implement Proper Error Handling
Handle token expiration, network errors, and authentication failures gracefully.
def call_api_with_retry(client, url, max_retries=3):
"""Call API with automatic retry on token expiration."""
for attempt in range(max_retries):
try:
return client.call_api(url)
except requests.HTTPError as e:
if e.response.status_code == 401:
# Token expired, clear cache and retry
client.access_token = None
if attempt < max_retries - 1:
continue
raise
except requests.RequestException as e:
# Network error, retry with exponential backoff
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
continue
raise6. Rotate Client Secrets Regularly
Implement a process to rotate client secrets periodically (e.g., every 90 days). Many authorization servers support multiple active secrets to enable zero-downtime rotation.
7. Monitor and Log Authentication Events
Log authentication attempts and monitor for suspicious activity:
- • Failed authentication attempts
- • Unusual access patterns
- • Token usage from unexpected locations
- • High-frequency token requests
Common Pitfalls & Solutions
Pitfall 1: Not Caching Access Tokens
Requesting a new token for every API call wastes resources and can hit rate limits.
# ❌ WRONG: Request token for every call
def call_api(url):
token = get_new_token() # Wasteful!
return requests.get(url, headers={'Authorization': f'Bearer {token}'})
# ✅ CORRECT: Cache and reuse tokens
class APIClient:
def __init__(self):
self.token = None
self.token_expires_at = 0
def get_token(self):
if not self.token or time.time() >= self.token_expires_at:
self.token = fetch_new_token()
return self.token
def call_api(self, url):
token = self.get_token()
return requests.get(url, headers={'Authorization': f'Bearer {token}'})Pitfall 2: Exposing Client Credentials
Never use Client Credentials flow in frontend applications or mobile apps where credentials can be extracted.
❌ NEVER DO THIS:
- • Embedding credentials in frontend JavaScript
- • Including credentials in mobile app binaries
- • Committing credentials to public repositories
- • Sharing credentials in documentation or examples
✅ INSTEAD:
- • Use Authorization Code flow for user-facing apps
- • Keep credentials on backend servers only
- • Use environment variables or secrets managers
- • Implement a backend proxy for frontend API calls
Pitfall 3: Ignoring Token Expiration
Not handling token expiration leads to failed API calls and poor user experience.
# ❌ WRONG: No expiration handling
token = get_token()
# Token might expire during long-running process
for item in large_dataset:
api_call(item, token) # Will fail when token expires!
# ✅ CORRECT: Check expiration before each call
def process_items(items):
for item in items:
token = get_valid_token() # Refreshes if needed
api_call(item, token)
# ✅ BETTER: Automatic retry on 401
def api_call_with_retry(url, token):
try:
return requests.get(url, headers={'Authorization': f'Bearer {token}'})
except requests.HTTPError as e:
if e.response.status_code == 401:
# Token expired, get new one and retry
new_token = get_new_token()
return requests.get(url, headers={'Authorization': f'Bearer {new_token}'})
raisePitfall 4: Using HTTP Instead of HTTPS
Sending credentials or tokens over HTTP exposes them to interception.
❌ INSECURE:
http://auth.example.com/oauth/token✅ SECURE:
https://auth.example.com/oauth/tokenPitfall 5: Not Validating Token Responses
Always validate the token response structure and handle errors properly.
# ❌ WRONG: Assuming success
response = requests.post(token_url, data=credentials)
token = response.json()['access_token'] # Might not exist!
# ✅ CORRECT: Validate response
response = requests.post(token_url, data=credentials)
response.raise_for_status() # Raise exception on HTTP error
token_data = response.json()
if 'access_token' not in token_data:
raise ValueError('No access token in response')
if 'expires_in' not in token_data:
raise ValueError('No expiration time in response')
token = token_data['access_token']
expires_in = token_data['expires_in']Real-World Use Cases
Here are common scenarios where Client Credentials flow is the right choice:
Microservices Communication
Service A needs to call Service B's API. Each service has its own client credentials and authenticates independently.
Microservices Authentication Flow
Service-to-service authentication pattern
Rendering diagram...
Scheduled Jobs and Cron Tasks
Background jobs that run on a schedule need to authenticate to access APIs. Client Credentials provides a simple, secure way to authenticate without user interaction.
CLI Tools and Scripts
Command-line tools that need to access APIs can use Client Credentials for authentication. Users configure credentials once, and the tool handles token management automatically.
IoT Devices and Sensors
IoT devices that send data to backend APIs can use Client Credentials for authentication. Each device has unique credentials and can be individually revoked if compromised.
Data Integration and ETL Pipelines
ETL processes that extract data from APIs, transform it, and load it into data warehouses use Client Credentials to authenticate with source and destination APIs.
Documenting OAuth Flows with AutEng
AutEng makes it easy to document OAuth flows with Mermaid sequence diagrams and code examples. Here's how to create comprehensive OAuth documentation:
Create Sequence Diagrams
Use Mermaid's sequence diagram syntax to visualize the authentication flow:
```mermaid
sequenceDiagram
participant Client
participant Auth as Auth Server
participant API
Client->>Auth: POST /token (credentials)
Auth->>Client: access_token
Client->>API: GET /resource (Bearer token)
API->>Client: Protected data
```Add Code Examples
Include working code examples in multiple languages to help developers implement the flow quickly.
Document Security Considerations
Always include security best practices and common pitfalls to help developers avoid security vulnerabilities.
Why AutEng for OAuth Documentation?
- • Real-time Preview: See your diagrams and code as you write
- • Version Control: Track changes to your authentication flows
- • Collaboration: Share documentation with your team
- • AI Generation: Generate diagrams and code examples with AI
Related Content
Comparing OAuth 2.0 Flows — Authorization Code vs Client Credentials
Complete comparison of OAuth 2.0 flows with sequence diagrams, implementation examples, and use cases
API Documentation
Document REST APIs and GraphQL schemas with examples
Markdown, Mermaid & KaTeX Syntax
Complete syntax reference for Markdown, Mermaid diagrams, and KaTeX math equations
Architecture Documentation
Document system architecture and design with diagrams and flows
Technical Specifications
Write ADRs, RFCs, and design documents
Ready to Start?
Start creating beautiful technical documentation with AutEng.
Get Started with AutEng