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:

  1. Create App Registration

      # Using Azure CLI
    az ad app create --display-name "My Service App"
      
  2. Configure API Permissions

    • Add application permissions (not delegated)
    • Grant admin consent
    • Common scopes:
      • https://graph.microsoft.com/.default
      • https://management.azure.com/.default
  3. 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:

  1. Verify application permissions in Azure Portal
  2. Ensure admin consent is granted
  3. Check token scopes match API requirements
  4. 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:

  1. Verify certificate path is absolute
  2. Check file permissions (readable by user)
  3. 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