Machine-to-machine (M2M) authentication is for server-to-server communication — your background worker calling your API, your AI agent calling a protected endpoint, or one microservice authenticating to another. Auth0's M2M flow uses the OAuth 2.0 client credentials grant: no user involved, just a client ID and secret.

When to Use M2M

  • An AI agent running in a background job needs to call your protected API
  • A cron job on Railway needs to call an endpoint on your Vercel deployment
  • Your embedding worker needs to write to a protected data service
  • Microservices need to authenticate to each other without user context

Step 1: Create an M2M Application

  1. Auth0 Dashboard > Applications > Create Application
  2. Choose Machine to Machine Applications
  3. Select the Auth0 API you want this application to access
  4. Select the scopes (permissions) this application needs
  5. Click Authorize

Auth0 gives you a Client ID and Client Secret. These are your application's credentials — treat the secret like a password.

Step 2: Request an Access Token

# Python — request an M2M token
import httpx, os
 
def get_m2m_token() -> str:
    response = httpx.post(
        f'https://{os.environ["AUTH0_DOMAIN"]}/oauth/token',
        json={
            'client_id':     os.environ['AUTH0_CLIENT_ID'],
            'client_secret': os.environ['AUTH0_CLIENT_SECRET'],
            'audience':      os.environ['AUTH0_AUDIENCE'],
            'grant_type':    'client_credentials',
        },
    )
    return response.json()['access_token']
 
# Use the token in an API call
token = get_m2m_token()
response = httpx.get(
    'https://api.yourapp.com/internal/data',
    headers={'Authorization': f'Bearer {token}'},
)
Cache the M2M token until it expires (typically 24 hours). Requesting a new token on every API call is wasteful — tokens are issued per-application and are expensive to generate at scale.

Step 3: Validate Tokens in Your API

# FastAPI — validate Auth0 JWT on incoming requests
from fastapi import Depends, HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt, os
from jwt import PyJWKClient
 
security = HTTPBearer()
jwks_client = PyJWKClient(
    f'https://{os.environ["AUTH0_DOMAIN"]}/.well-known/jwks.json'
)
 
def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)):
    token = credentials.credentials
    signing_key = jwks_client.get_signing_key_from_jwt(token)
 
    try:
        payload = jwt.decode(
            token,
            signing_key.key,
            algorithms=['RS256'],
            audience=os.environ['AUTH0_AUDIENCE'],
            issuer=f'https://{os.environ["AUTH0_DOMAIN"]}/'
        )
    except jwt.exceptions.InvalidTokenError as e:
        raise HTTPException(status_code=401, detail=str(e))
 
    return payload
 
@app.get('/internal/data')
def internal_data(token: dict = Depends(verify_token)):
    # token['sub'] contains the client ID, not a user ID
    return {'data': 'sensitive internal data'}

Checking Scopes for Fine-Grained Access

def require_scope(required_scope: str):
    def scope_checker(token: dict = Depends(verify_token)):
        scopes = token.get('scope', '').split()
        if required_scope not in scopes:
            raise HTTPException(status_code=403, detail='Insufficient scope')
        return token
    return scope_checker
 
@app.delete('/internal/documents/{doc_id}')
def delete_document(
    doc_id: str,
    token: dict = Depends(require_scope('delete:documents'))
):
    # Only M2M clients with the delete:documents scope can call this
    delete_doc(doc_id)
    return {'deleted': doc_id}
Metadata Value
Title Machine-to-Machine Auth with Auth0: Securing Internal APIs and Microservices
Tool Auth0
Primary SEO keyword auth0 machine to machine authentication
Secondary keywords auth0 M2M, auth0 client credentials, auth0 API security, auth0 microservices
Estimated read time 8 minutes
Research date 2026-04-14