Client Credentials Flow
Client Credentials Flow
The Client Credentials flow is designed for service-to-service authentication where no user interaction is required. This is the most common flow for automation, CI/CD pipelines, and background services.
Overview
Use this flow when:
- Your application runs without user interaction
- You’re authenticating a service, not a user
- You need application-level permissions
- Running in CI/CD pipelines or automated scripts
Authentication methods:
- Client Secret - Shared secret (less secure, easier setup)
- Certificate - Public/private key pair (more secure, recommended for production)
Quick Start
Using Client Secret
# Create profile with client secret
entra-auth-cli create-profile
# Generate token
entra-auth-cli get-token --profile myapp
Using Certificate
# Create profile with certificate
entra-auth-cli create-profile --use-certificate
# Generate token
entra-auth-cli get-token --profile myapp-cert
Configuration
Profile Setup
When creating a profile for client credentials flow:
entra-auth-cli create-profile
You’ll be prompted for:
- Profile Name: Identifier for this configuration
- Tenant ID: Your Microsoft Entra tenant ID
- Client ID: Application (client) ID from Azure
- Authentication Method: Client secret or certificate
- Scopes: Default API permissions
Azure App Registration
Required Azure configuration:
Create App Registration
# Using Azure CLI az ad app create --display-name "My Service App"Configure API Permissions
- Add application permissions (not delegated)
- Grant admin consent
- Common scopes:
https://graph.microsoft.com/.defaulthttps://management.azure.com/.default
Add Credentials
Option A: Client Secret
az ad app credential reset --id <app-id>Option B: Certificate
az ad app credential reset --id <app-id> --cert @cert.pem
Usage Examples
Basic Token Request
# Using default profile
entra-auth-cli get-token
# Using specific profile
entra-auth-cli get-token --profile production
# Override scopes
entra-auth-cli get-token --scope https://graph.microsoft.com/.default
With Microsoft Graph
# Get token for Graph API
TOKEN=$(entra-auth-cli get-token --scope https://graph.microsoft.com/.default --output json | jq -r .access_token)
# Use token
curl -H "Authorization: Bearer $TOKEN" \
https://graph.microsoft.com/v1.0/users
With Azure Management
# Get token for Azure Management
TOKEN=$(entra-auth-cli get-token --scope https://management.azure.com/.default --output json | jq -r .access_token)
# List subscriptions
curl -H "Authorization: Bearer $TOKEN" \
https://management.azure.com/subscriptions?api-version=2020-01-01
In Scripts
#!/bin/bash
set -euo pipefail
# Function to get token with error handling
get_token() {
local scope="${1:-https://graph.microsoft.com/.default}"
local max_retries=3
local retry=0
while [ $retry -lt $max_retries ]; do
if TOKEN=$(entra-auth-cli get-token --scope "$scope" --output json 2>/dev/null); then
echo "$TOKEN" | jq -r .access_token
return 0
fi
retry=$((retry + 1))
sleep $((retry * 2))
done
echo "Failed to get token after $max_retries attempts" >&2
return 1
}
# Use the token
if TOKEN=$(get_token); then
curl -H "Authorization: Bearer $TOKEN" \
https://graph.microsoft.com/v1.0/me
fi
CI/CD Integration
GitHub Actions
name: Deploy with Token
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Entra Auth Cli
run: |
wget https://github.com/garrardkitchen/entra-auth-cli/releases/latest/download/entra-auth-cli-linux-amd64
chmod +x entra-auth-cli-linux-amd64
sudo mv entra-auth-cli-linux-amd64 /usr/local/bin/entra-auth-cli
- name: Create Profile
env:
TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
run: |
entra-auth-cli create-profile \
--name ci \
--tenant-id "$TENANT_ID" \
--client-id "$CLIENT_ID" \
--client-secret "$CLIENT_SECRET" \
--scope https://management.azure.com/.default
- name: Get Token and Deploy
run: |
TOKEN=$(entra-auth-cli get-token --profile ci --output json | jq -r .access_token)
# Use token for deployment
Azure Pipelines
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Bash@3
displayName: 'Install Entra Auth Cli'
inputs:
targetType: 'inline'
script: |
wget https://github.com/garrardkitchen/entra-auth-cli/releases/latest/download/entra-auth-cli-linux-amd64
chmod +x entra-auth-cli-linux-amd64
sudo mv entra-auth-cli-linux-amd64 /usr/local/bin/entra-auth-cli
- task: Bash@3
displayName: 'Get Token'
env:
TENANT_ID: $(AZURE_TENANT_ID)
CLIENT_ID: $(AZURE_CLIENT_ID)
CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
inputs:
targetType: 'inline'
script: |
entra-auth-cli create-profile \
--name pipeline \
--tenant-id "$TENANT_ID" \
--client-id "$CLIENT_ID" \
--client-secret "$CLIENT_SECRET"
TOKEN=$(entra-auth-cli get-token --profile pipeline --output json | jq -r .access_token)
echo "##vso[task.setvariable variable=ACCESS_TOKEN;isSecret=true]$TOKEN"
- task: Bash@3
displayName: 'Deploy Application'
inputs:
targetType: 'inline'
script: |
# Use $(ACCESS_TOKEN) in subsequent commands
curl -H "Authorization: Bearer $(ACCESS_TOKEN)" ...
Security Best Practices
Certificate-Based Authentication
Always prefer certificates over client secrets in production:
# Create certificate
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
# Create profile with certificate
entra-auth-cli create-profile \
--name secure-app \
--use-certificate \
--certificate-path cert.pem \
--private-key-path key.pem
Secret Management
Don’t hardcode secrets:
# ❌ Bad - hardcoded
entra-auth-cli create-profile --client-secret "my-secret-123"
# ✅ Good - from environment
entra-auth-cli create-profile --client-secret "${CLIENT_SECRET}"
# ✅ Better - from secure vault
CLIENT_SECRET=$(az keyvault secret show --vault-name myvault --name client-secret --query value -o tsv)
entra-auth-cli create-profile --client-secret "${CLIENT_SECRET}"
Least Privilege
Only request the permissions you need:
# ❌ Too broad
entra-auth-cli get-token --scope https://graph.microsoft.com/.default
# ✅ Specific permission
entra-auth-cli get-token --scope https://graph.microsoft.com/User.Read.All
Troubleshooting
“Unauthorized” Errors
Problem: 401 Unauthorized when using token
Solutions:
- Verify application permissions in Azure Portal
- Ensure admin consent is granted
- Check token scopes match API requirements
- Verify application has correct role assignments
# Inspect token to verify scopes
entra-auth-cli inspect --profile myapp
Certificate Not Found
Problem: Profile created but certificate not accessible
Solutions:
- Verify certificate path is absolute
- Check file permissions (readable by user)
- Ensure certificate format is correct (PEM/PFX)
# Verify certificate
openssl x509 -in cert.pem -text -noout
Token Expired
Problem: Token works initially but fails later
Solution: Tokens expire after 1 hour. Implement refresh logic:
# Check if token is still valid
is_token_valid() {
local token="$1"
local exp=$(echo "$token" | jq -R 'split(".") | .[1] | @base64d | fromjson | .exp')
local now=$(date +%s)
[ "$exp" -gt "$now" ]
}
# Get new token if expired
if ! is_token_valid "$TOKEN"; then
TOKEN=$(entra-auth-cli get-token --profile myapp --output json | jq -r .access_token)
fi
Performance Optimization
Token Caching
Entra Auth Cli automatically caches tokens. Reuse tokens within their lifetime:
# First call gets new token
entra-auth-cli get-token --profile myapp # ~500ms
# Subsequent calls use cached token
entra-auth-cli get-token --profile myapp # ~50ms
Parallel Requests
When making multiple API calls with the same token:
# Get token once
TOKEN=$(entra-auth-cli get-token --output json | jq -r .access_token)
# Use for multiple parallel requests
{
curl -H "Authorization: Bearer $TOKEN" https://graph.microsoft.com/v1.0/users &
curl -H "Authorization: Bearer $TOKEN" https://graph.microsoft.com/v1.0/groups &
curl -H "Authorization: Bearer $TOKEN" https://graph.microsoft.com/v1.0/applications &
wait
}
Next Steps
- Certificate Authentication - Use certificates instead of secrets
- CI/CD Integration - Complete CI/CD examples
- Security Hardening - Production security checklist
- Microsoft Graph Recipes - Common Graph API patterns