Auth0 Machine to Machine — API Authentication for Backend Services
In this tutorial, you will learn about Auth0 Machine to Machine. We cover key concepts, practical examples, and best practices to help you master this topic.
Auth0 Machine to Machine (M2M) authentication uses the OAuth 2.0 client credentials grant to enable secure service-to-service communication, where backend services obtain access tokens without user interaction.
What You'll Learn
By the end of this lesson you will create an M2M application, configure API audiences and scopes, obtain tokens using the client credentials flow, and validate M2M tokens on your backend.
Why It Matters
Modern applications consist of multiple services that need to communicate securely. M2M authentication ensures that only authorized services can access internal APIs, without requiring a user to be logged in.
Real-World Use
DodaZIP's backend services use M2M auth for inter-service communication. The file processing service authenticates to the analytics service, and the notification service authenticates to the email service.
flowchart LR
S1[Processing Service] -->|Client Credentials| A[Auth0]
A -->|Access Token| S1
S1 -->|API Call + Token| S2[Analytics Service]
S2 -->|Validate Token| A
S2 -->|Response| S1
style A fill:#eb5424,color:#fff
Creating an M2M Application
Register a Machine to Machine application in Auth0.
# m2m_app.py
# Creating an M2M application
def create_m2m_app():
print("M2M Application Setup:")
print()
print("In Auth0 Dashboard:")
print(" 1. Applications > Create Application")
print(" 2. Name: Production API Service")
print(" 3. Type: Machine to Machine Applications")
print(" 4. Choose an API (or create one)")
print(" 5. Select scopes for this application")
print()
print("Result:")
print(" - Client ID: gj7R... (public identifier)")
print(" - Client Secret: 3p8k... (keep secret!)")
print(" - Grant Type: client_credentials")
print()
print("Security notes:")
print(" - Store Client Secret in secrets manager")
print(" - Rotate secrets periodically")
print(" - Use different M2M apps for different services")
create_m2m_app()
Client Credentials Flow
Obtain tokens using the client credentials grant.
# POST /oauth/token
curl --request POST \
--url 'https://YOUR_TENANT.us.auth0.com/oauth/token' \
--header 'content-type: application/json' \
--data '{
"client_id": "YOUR_M2M_CLIENT_ID",
"client_secret": "YOUR_M2M_CLIENT_SECRET",
"audience": "https://api.dodatech.com",
"grant_type": "client_credentials"
}'
# client_credentials.py
# Obtain M2M access token
import requests
import os
def get_m2m_token():
domain = os.getenv("AUTH0_DOMAIN", "your-tenant.us.auth0.com")
client_id = os.getenv("AUTH0_M2M_CLIENT_ID", "your-m2m-id")
client_secret = os.getenv("AUTH0_M2M_CLIENT_SECRET", "your-m2m-secret")
audience = os.getenv("AUTH0_AUDIENCE", "https://api.dodatech.com")
response = requests.post(
f"https://{domain}/oauth/token",
json={
"client_id": client_id,
"client_secret": client_secret,
"audience": audience,
"grant_type": "client_credentials",
}
)
if response.status_code == 200:
token_data = response.json()
print("M2M token obtained:")
print(f" Access token: {token_data['access_token'][:30]}...")
print(f" Token type: {token_data['token_type']}")
print(f" Expires in: {token_data['expires_in']} seconds")
print(f" Scopes: {token_data.get('scope', 'default')}")
return token_data["access_token"]
else:
print(f"Failed: {response.text}")
return None
get_m2m_token()
Configuring API and Scopes
Define your API and the scopes M2M applications can request.
# api_scopes.py
# API and scope configuration
def define_api_scopes():
print("API Definition:")
print(" Name: DodaTech API")
print(" Identifier: https://api.dodatech.com")
print()
print("Scopes (permissions):")
scopes = [
("read:files", "Read file metadata and content"),
("write:files", "Create and update files"),
("delete:files", "Remove files"),
("read:users", "Read user profiles"),
("process:jobs", "Execute processing jobs"),
("read:analytics", "Read analytics data"),
]
print(f" {'Scope':25s} | {'Description'}")
print(" " + "-" * 55)
for scope, desc in scopes:
print(f" {scope:25s} | {desc}")
print()
print("Assign scopes to each M2M application:")
print(" Processing Service: read:files, write:files, process:jobs")
print(" Analytics Service: read:files, read:analytics")
print(" Notification Service: read:users")
define_api_scopes()
Validating M2M Tokens
Validate M2M tokens in your backend service.
# validate_m2m.py
# Validate M2M access tokens
import jwt
import requests
def validate_m2m_token(token, expected_audience, expected_scopes):
domain = os.getenv("AUTH0_DOMAIN", "your-tenant.us.auth0.com")
# Get JWKS from Auth0
jwks_url = f"https://{domain}/.well-known/jwks.json"
jwks = requests.get(jwks_url).json()
try:
# Decode and verify the token
payload = jwt.decode(
token,
jwks,
algorithms=["RS256"],
audience=expected_audience,
issuer=f"https://{domain}/"
)
print("Token is valid!")
print(f" Client ID: {payload.get('sub')}")
print(f" Issued at: {payload.get('iat')}")
print(f" Expires: {payload.get('exp')}")
# Check scopes
token_scopes = payload.get("scope", "").split()
has_all_scopes = all(s in token_scopes for s in expected_scopes)
if has_all_scopes:
print(f" Scopes: {', '.join(expected_scopes)} [AUTHORIZED]")
return True
else:
print(f" Missing required scopes")
return False
except jwt.ExpiredSignatureError:
print("Token has expired")
return False
except jwt.InvalidAudienceError:
print("Token audience does not match")
return False
except Exception as e:
print(f"Token validation failed: {e}")
return False
validate_m2m_token("sample_token", "https://api.dodatech.com", ["read:files"])
Common Mistakes
Client Secret in source code: Never hardcode M2M client secrets. Use environment variables or a secrets manager.
Not rotating client secrets: M2M secrets should be rotated regularly. Set a rotation schedule and automate the process.
Over-scoping M2M applications: Assign only the scopes each service needs (principle of Least Privilege).
Not validating tokens in the receiving service: Every service should validate the token's signature, audience, and scopes.
Reusing the same M2M application for multiple services: Each service should have its own M2M application with its own credentials and scopes.
Practice Questions
What OAuth grant type does M2M use? The client credentials grant, where the client authenticates with its own credentials.
What are M2M applications used for? Service-to-service communication where no user is involved, such as backend Microservices.
How do you restrict what an M2M application can do? By assigning specific scopes (permissions) to the M2M application.
Why should each service have its own M2M application? For least privilege -- each service gets only the permissions it needs, and credentials can be rotated independently.
Challenge: Set up M2M authentication for a three-service architecture: an API Gateway, a user service, and a file processing service, each with appropriate scopes.
FAQ
Mini Project
Create an M2M authentication setup for a three-tier architecture: a client service, a data service, and an analytics service. Configure each with appropriate scopes and implement token validation in each service.
def m2m_architecture():
services = {
"API Gateway": {
"scopes": ["read:files", "write:files"],
"audience": "https://api.dodatech.com/data",
},
"User Service": {
"scopes": ["read:users", "write:users"],
"audience": "https://api.dodatech.com/users",
},
"Analytics Service": {
"scopes": ["read:analytics"],
"audience": "https://api.dodatech.com/analytics",
},
}
print("M2M Service Configuration:")
for service, config in services.items():
print(f" {service:20s} | Scopes: {', '.join(config['scopes']):40s} | Audience: {config['audience']}")
m2m_architecture()
What's Next
Next: Management API for programmatic tenant management.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro