M2M Client Credentials
Machine-to-machine (M2M) authentication is used when a backend service or automated process needs to call an API without a human user present. Auris implements the OAuth2 client_credentials grant (RFC 6749 Section 4.4), issuing access tokens directly to confidential clients.
Common use cases:
- A background job that needs to read user data from the Auris Management API
- A microservice that validates permissions for API requests
- A CI/CD pipeline that manages roles and application configuration
- A backend service calling another service that validates Auris tokens
How It Works
The client_credentials flow does not involve a user, a login page, or a redirect. The client sends its credentials directly to the token endpoint and receives an access token:
- Client sends
client_id,client_secret,grant_type=client_credentials, and optionalscopeto the token endpoint - Auris validates the credentials and checks that the requested scopes are permitted for the application
- Auris issues a JWT access token with
type: 'm2m'and the granted scopes in thescopeclaim - The client presents the access token as a Bearer token on downstream API calls
Tokens are short-lived (default: 60 minutes) and should be cached and reused until expiry.
Console Setup
Create an M2M Application
In the Auris Console, go to Applications → Create Application and select M2M as the application type.
M2M applications do not have redirect URIs or login pages — only credentials and scopes.
Configure Allowed Scopes
Under the M2M Scopes tab on the application detail page, select which scopes the application is permitted to request. Available scopes are organized by resource (e.g., read:users, manage:roles, view:organizations).
Copy Credentials
From the Credentials tab, copy the Client ID and Client Secret.
Client secrets are displayed only once at creation time. Store the secret securely (e.g., in a secrets manager or environment variable). If the secret is lost, use the Rotate Secret button to generate a new one.
Store Credentials Securely
Set the credentials as environment variables in your deployment environment. Never hardcode them in source code or commit them to version control.
AURIS_CLIENT_ID=your-m2m-client-id
AURIS_CLIENT_SECRET=your-m2m-client-secret
AURIS_DOMAIN=auth.yourdomain.comImplementation
JavaScript
import { AurisClient } from '@auris/js'
const auris = new AurisClient({
domain: process.env.AURIS_DOMAIN,
clientId: process.env.AURIS_CLIENT_ID,
})
// Request an M2M access token with specific scopes
const token = await auris.getM2MToken(
process.env.AURIS_CLIENT_SECRET,
['read:users', 'manage:roles'],
)
console.log('Access token:', token.accessToken)
console.log('Expires in:', token.expiresIn, 'seconds')
// Use the token to call the Auris Management API
const usersResponse = await fetch('https://auth.yourdomain.com/api/users', {
headers: { Authorization: `Bearer ${token.accessToken}` },
})
const users = await usersResponse.json()Token Format
M2M access tokens are JWTs with the following claims:
{
"iss": "https://auth.yourdomain.com",
"sub": "your-client-id",
"aud": "https://auth.yourdomain.com",
"exp": 1735000000,
"iat": 1734996400,
"type": "m2m",
"scope": "read:users manage:roles",
"clientId": "your-client-id"
}The type: 'm2m' claim distinguishes these tokens from user tokens (type: 'user'). Your APIs can use this claim to differentiate between user and service requests.
Token Caching
M2M tokens should be cached for their full validity period to avoid unnecessary network calls to the token endpoint:
class TokenCache {
#token = null
#expiresAt = 0
async getToken(auris, clientSecret, scopes) {
// Return cached token if still valid (with 60s buffer)
if (this.#token && Date.now() < this.#expiresAt - 60_000) {
return this.#token
}
const result = await auris.getM2MToken(clientSecret, scopes)
this.#token = result.accessToken
this.#expiresAt = Date.now() + result.expiresIn * 1000
return this.#token
}
}
const cache = new TokenCache()
// In your API calls:
const token = await cache.getToken(auris, clientSecret, ['read:users'])DPoP Token Binding
For environments requiring sender-constrained tokens, Auris supports DPoP (Demonstrating Proof of Possession, RFC 9449). DPoP binds the access token to a specific client’s public key — a stolen token cannot be used by a different client without the corresponding private key.
Enabling DPoP for M2M applications:
- Enable DPoP on the M2M application in Console → Applications → [App] → Settings → Require DPoP
- Generate a key pair in your service and include a DPoP proof header with each token request and API call
import { createDpopProof } from '@auris/js'
// Generate a DPoP key pair (do this once at service startup)
const dpopKeyPair = await crypto.subtle.generateKey(
{ name: 'ECDSA', namedCurve: 'P-256' },
false, // not extractable
['sign', 'verify'],
)
// Request a token with DPoP
const dpopProof = await createDpopProof(dpopKeyPair, 'POST', tokenEndpointUrl)
const tokenResponse = await fetch(tokenEndpointUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'DPoP': dpopProof,
},
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret,
}),
})Protecting Your Own APIs with M2M Tokens
If your backend APIs accept Auris M2M tokens from other services, validate the token signature using the Auris JWKS endpoint:
import { verifyToken } from '@auris/js/jwt-verify'
export async function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing Authorization header' })
}
const token = authHeader.slice(7)
try {
const payload = await verifyToken(token, {
jwksUrl: 'https://auth.yourdomain.com/.well-known/jwks.json',
issuer: 'https://auth.yourdomain.com',
})
// Check it's an M2M token (not a user token)
if (payload.type !== 'm2m') {
return res.status(403).json({ error: 'User tokens not permitted on this endpoint' })
}
// Check required scopes
const scopes = payload.scope?.split(' ') ?? []
if (!scopes.includes('call:your-service')) {
return res.status(403).json({ error: 'Insufficient scope' })
}
req.clientId = payload.sub
next()
} catch (err) {
return res.status(401).json({ error: 'Invalid token' })
}
}API Endpoints
/api/auth/tokenToken endpoint. Set grant_type=client_credentials, client_id, client_secret, and optional scope. Returns an access token and expiry.
/api/applications/:id/m2m-scopesRequires: manage:applicationsList the allowed scopes for an M2M application.
/api/applications/:id/m2m-scopesRequires: manage:applicationsUpdate the allowed scopes for an M2M application.
Related Guides
- Hosted Login (PKCE) — Interactive user authentication
- Roles and Permissions (RBAC) — Controlling what M2M clients can do
- Fine-Grained Authorization — Object-level access control for M2M services