Skip to Content
GuidesAuthenticationM2M Client Credentials

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:

  1. Client sends client_id, client_secret, grant_type=client_credentials, and optional scope to the token endpoint
  2. Auris validates the credentials and checks that the requested scopes are permitted for the application
  3. Auris issues a JWT access token with type: 'm2m' and the granted scopes in the scope claim
  4. 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 ApplicationsCreate 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.com

Implementation

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:

  1. Enable DPoP on the M2M application in Console → Applications → [App] → Settings → Require DPoP
  2. 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

POST/api/auth/token

Token endpoint. Set grant_type=client_credentials, client_id, client_secret, and optional scope. Returns an access token and expiry.

GET/api/applications/:id/m2m-scopesRequires: manage:applications

List the allowed scopes for an M2M application.

POST/api/applications/:id/m2m-scopesRequires: manage:applications

Update the allowed scopes for an M2M application.