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
- Auth0 Dashboard > Applications > Create Application
- Choose Machine to Machine Applications
- Select the Auth0 API you want this application to access
- Select the scopes (permissions) this application needs
- 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 |