M2M Authorization: Authenticate Apps, APIs, and Web Services
OAuth 2.0 client credentials grant explained: how services authenticate to each other, request scoped tokens, and call APIs without a user in the loop.

Plenty of systems need to talk to each other without a human in the loop. A daemon syncing data to a backend, a CLI client calling an internal service, an IoT device pushing telemetry, a partner integration pulling reports. Email/password and social login do not fit any of those flows. Machine-to-machine (M2M) authorization does.
I have built and run CIAM infrastructure that handles a billion identities, and M2M authorization is one of the simpler pieces to get right once you understand the OAuth 2.0 client credentials grant. This post walks through the pattern, the flow, and how to wire it into your own services or a third-party identity platform.
What is M2M authorization?
M2M authorization is the process by which one system proves its identity to another system and receives scoped permission to act. There is no end user involved. A service authenticates itself, gets back an access token, and uses that token on subsequent API calls.
The standard mechanism is the OAuth 2.0 client credentials grant (RFC 6749). The calling service holds a client_id and client_secret, exchanges them at a token endpoint, and receives a short-lived JWT that encodes its allowed scopes.
When to use it
- Service-to-service calls inside a microservices fleet.
- Scheduled jobs reading or writing to a shared data store.
- CLI tools that operate against internal APIs.
- IoT device fleets reporting to a central ingest API.
- Partner or external integrations calling your business APIs with scoped permission.
Each calling service should get its own client credentials. If three jobs need write, read, and admin access respectively, that is three M2M clients with three different scope sets, not one shared key.
The client credentials grant flow
The flow has three steps and no user interaction:
- The calling service POSTs
client_id,client_secret, and an optionalaudienceto the authorization server's token endpoint. - The authorization server validates the credentials, checks the requested scopes against what the client is allowed, and returns a signed JWT access token with a short TTL.
- The calling service includes that token in the
Authorization: Bearerheader on every API call. The receiving API verifies the JWT signature and enforces the scopes encoded in it. The client secret is never sent again after step one.
Requesting an access token
A typical token request against an OAuth 2.0 authorization server looks like this:
POST https://auth.example.com/oauth/token
Content-Type: application/json
{
"grant_type": "client_credentials",
"client_id": "<YOUR_CLIENT_ID>",
"client_secret": "<YOUR_CLIENT_SECRET>",
"audience": "https://api.example.com"
}The response carries the bearer token and its lifetime:
{
"access_token": "eyJz93a...k4laUWw",
"token_type": "Bearer",
"expires_in": 86400
}Decoded, the JWT contains the issuer, the client subject, the audience, the granted scopes, and expiry timestamps:
{
"iss": "https://auth.example.com/",
"sub": "<client_id>@client",
"aud": "https://api.example.com",
"exp": 1311281970,
"iat": 1311281670,
"scp": ["profile:read", "profile:create"],
"gty": "client_credentials"
}Calling an API with the token
curl --request GET \
--url https://api.example.com/v2/users/{uid} \
--header 'authorization: Bearer eyJhb...jVZ2w'The receiving service verifies the signature against the authorization server's public key (usually fetched from a JWKS endpoint), checks aud, iss, and exp, and then enforces the scopes in scp against the requested operation.
Operational practices that matter
- Scope tightly. A read job should not hold write scopes. Granular scopes limit blast radius if a client secret leaks.
- Rotate secrets. Treat client secrets like any other credential. Rotate them on a schedule and on personnel changes.
- Short token TTLs. Most M2M tokens should live minutes to hours, not days. Services can re-issue cheaply.
- Cache tokens in memory. Do not request a new token on every call. Reuse the token until close to expiry, then refresh.
- Audit token issuance. Log every token grant with client ID, requested audience, and scopes. This is your trail when something goes wrong.
- Never embed secrets in client-side code. Client credentials are for confidential clients only. Anything shipped to a browser or mobile device should use a different grant.
Why this pattern is worth the setup
- Secure, scoped access across internal and external systems with no user in the loop.
- Granular permissions per calling service.
- Standardized on OAuth 2.0, so any compliant client library works.
- Easy to revoke: rotate or disable a single client without touching others.
Conclusion
M2M authorization is the backbone of autonomous service communication. The client credentials grant gives you a small, well-understood primitive: one service proves itself, gets a scoped token, and acts within those limits. Any modern CIAM platform or general-purpose OAuth 2.0 authorization server implements it. The work is in the operational discipline: tight scopes, short TTLs, rotated secrets, and good audit logs.
Get the newsletter
New writing on identity, AI security, and building software, delivered when it ships. No tracking pixels, no funnels, unsubscribe with one click.