Machine-to-machine
Get an access token for the SenseCrypt Management API with the OAuth 2.0 client-credentials grant — register an M2M app, bind roles, call the token endpoint, and use the token.
Use the client-credentials grant when a backend service needs to call the SenseCrypt Management API with no user present — for example to automate tenant administration or drive SCIM provisioning programmatically. The result is an access token whose capabilities come from the roles bound to the machine app.
Two different things share this grant. An M2M app (a service account, client_id of the form m2m_…) mints a token for the tenant's Management API, audience {issuer}/v1/admin/ — that is what this guide covers. A registered OIDC application can also use client_credentials to mint a token for one of your resource servers, but only when Allow the client credentials grant (allow_client_credentials) is turned on for it — it is off by default. That path has its own contract, and OIDC & OAuth 2.0 → Grant types is where it is written down; nothing on this page applies to it. client_credentials is listed in grant_types_supported on Standard and FAPI issuers alike.
Prerequisites
- A SenseCrypt tenant.
- An M2M app created in the admin console, with one or more M2M roles bound to it.
1. Create an M2M app and bind roles
In the admin console, create an M2M app. You receive a client_id (of the form m2m_...) and a client_secret (store the secret securely — treat it like any credential). In a FAPI 2.0 tenant you can instead give the app a key or a certificate and no secret at all — see FAPI 2.0 tenants below; the rest of this section applies either way. Bind the roles that grant the capabilities this service needs; the token's capabilities are the union of its bound roles, recomputed on every mint, so role changes take effect on the next token.
2. Request a token
The M2M app is tenant-scoped, so request a token from the tenant's issuer token endpoint (resolve it from discovery; conventionally /v1/idp/oidc/token). The requested audience must be the tenant's Management API identifier — the exact value {issuer}/v1/admin/ (note the trailing slash):
curl -X POST {token_endpoint} \
-d grant_type=client_credentials \
-d client_id={client_id} \
-d client_secret={client_secret} \
-d audience={issuer}/v1/admin/Successful response:
{
"access_token": "eyJhbGciOiJFUzI1Ni…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "oidc_apps:read auth_users:create …"
}The ES256 access token carries:
subin the form{client_id}@clients,azp= yourclient_id, andgty=client-credentials,scopeand apermissionsclaim = the capabilities of the app's bound roles,token_use: "access", ajti, and an expiry from the app's configured access-token lifetime (3600s by default).
If the audience is not the tenant's Management API identifier, the request is rejected with invalid_target ("Audience must be the management API identifier"). Both audience (Auth0 spelling) and resource (RFC 8707) are accepted; if you send both, resource wins.
3. Call the Management API
Send the access token as a bearer credential to the Management API:
curl {management_api_url} \
-H "Authorization: Bearer {access_token}"Each capability is a fine-grained entity:verb (for example oidc_apps:read, auth_users:create). The token can only act within the one tenant it was minted for, and only up to the capabilities its roles carry.
Audience string vs. call host. The audience you request is the Management API identifier {issuer}/v1/admin/ (built from the issuer host). The admin API itself is served on your tenant's admin host, which may differ from the issuer host. Request the audience exactly as {issuer}/v1/admin/, but confirm the actual {management_api_url} admin host with your operator. See the API Reference for the admin endpoints.
End to end, the three steps chain from token mint to an authorized Management API call:
FAPI 2.0 tenants: sign in with a key or a certificate
In a FAPI 2.0 tenant the create dialog also offers three ways to sign in without a secret. Secret-based apps stay available beside them and work as before, but their tokens are plain bearer tokens: anyone who holds one can call the Management API with it until it expires. The console's M2M apps page marks each of them Secret · Bearer in a FAPI 2.0 tenant and counts the enabled ones in one notice. To move one, see Move a secret app to a key or certificate.
| Sign-in method | You register | At /token the app presents |
|---|---|---|
Private key (private_key_jwt) | One public key (JWK), ES256 on P-256 or PS256 on RSA ≥ 2048 bits, with alg set | A client_assertion signed with its private key: iss = sub = client_id, aud = the issuer string, a fresh jti, a short exp |
Certificate from your CA (tls_client_auth) | Your issuing CA certificate(s) and the subject DN the app's certificate carries | Its client certificate, over the mutual TLS token endpoint (mtls_endpoint_aliases.token_endpoint in discovery) |
Self-signed certificate (self_signed_tls_client_auth) | The app's own certificate | That certificate, over the mutual TLS token endpoint |
Every token such an app gets is sender-constrained. You choose how when you create it:
- DPoP key (the default for a private key). Send a DPoP proof with every token request. The token comes back with
token_type: DPoPand acnf.jkt, and every Management API call must useAuthorization: DPoP <token>plus a fresh proof for that call. - Client certificate (the default for a certificate). The token carries
cnf.x5t#S256and works only on a mutual TLS connection that presents the same certificate, so call the Management API through the mutual TLS listener too.
# Private key + DPoP
curl -X POST {token_endpoint} \
-H "DPoP: <proof: htm=POST, htu={token_endpoint}>" \
-d grant_type=client_credentials \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
-d client_assertion=<JWT signed with the app's private key> \
-d audience={issuer}/v1/admin/
# Certificate, bound to the certificate
curl -X POST --cert client.crt --key client.key {mtls_token_endpoint} \
-d grant_type=client_credentials \
-d client_id={client_id} \
-d audience={issuer}/v1/admin/The first DPoP request is answered 400 use_dpop_nonce with a DPoP-Nonce header. Retry with a proof that carries that nonce. For a private-key app, also send a new client_assertion: the first one's jti has already been used, and a replayed jti is invalid_client. Each successful token response carries a fresh DPoP-Nonce for the next request.
The app's keys and certificates rotate without an outage. The new one works at once, and the old one keeps working for 24 hours or stops now. See Rotating client keys and certificates.
Move a secret app to a key or certificate
An app's sign-in method is set when the app is created and cannot be changed afterwards. To move a service off a client secret, give it a new app and retire the old one:
- Create a new app in the same tenant that signs in with a private key or a certificate (the table above). Bind the same roles and set the same token lifetime. The new app has its own
client_id. - Switch your service to the new
client_idand sign-in method. This is a code change as well as a configuration change: a DPoP-bound token needsAuthorization: DPoP <token>and a fresh proof on every Management API call, and a certificate-bound token works only over mutual TLS with the same certificate. - Check that the old app is idle. Its last used time on the M2M apps page stops moving once nothing signs in with its secret.
- Disable the old app. It can no longer get a token, the Management API refuses the tokens it already holds, and the console notice stops counting it. Disabling can be undone, so you can turn it back on if something still depended on it.
- Delete the old app when you are sure. Deleting cannot be undone.
SCIM via M2M
An M2M role can also grant the SCIM capabilities (scim:read / scim:write). A token carrying those can call the SCIM endpoints directly — an alternative to an opaque ssct_ SCIM token.
Verify it worked
- Decode the token (e.g. on jwt.io or with your JWT library) and confirm
subis{client_id}@clients,audis{issuer}/v1/admin/, andpermissionslists the capabilities you expect from the bound roles. - Make one read call you're authorized for (for example listing OIDC apps if the token has
oidc_apps:read) and confirm a200. - Call an endpoint your roles don't cover and confirm you're denied — that proves the capability gate is enforcing, not that your token is broken.
Troubleshooting
invalid_target. Theaudienceisn't exactly{issuer}/v1/admin/(watch the trailing slash and the issuer host).invalid_client. Wrongclient_id/client_secret, a disabled app, or a credential from a different tenant. All of these collapse to a single opaqueinvalid_client(HTTP 401) by design. For a FAPI app, the same answer covers a secret sent to an app that has none, an assertion signed with a key that isn't current (or still in its grace window), a replayed assertionjti, and a certificate that doesn't match the registration.use_dpop_nonce/invalid_dpop_proofat/token. The app's tokens are DPoP-bound: send a proof, and retry once with the returnedDPoP-Nonce. A private-key app also needs a new assertion for the retry.- 401 with
kind: sender_constraintfrom the Management API. A bound token was sent without its proof or certificate: a DPoP-bound token asBearer, without aDPoPheader, or with a proof from another key; or a certificate-bound token off the mutual TLS listener or with another certificate. TheWWW-Authenticateheader says what was expected. - Authorized token, but a call returns 403. The token is valid but its bound roles don't include the capability that endpoint needs. Add the role in the console and mint a fresh token (capabilities are recomputed per mint).
- No response at all from the mutual TLS token endpoint. A known cause is a client certificate that is not X.509 v3. See No response from a mutual TLS endpoint.
- Calls 404 / connection fails. You may be calling the issuer host instead of the admin host. The audience string and the call URL can differ — see the note in step 3.
Notes
- Client-credentials mints no refresh token and no ID token — request a fresh access token when it expires.
- Secrets support a rotation grace window, so you can roll a secret without an outage. Keys and certificates do too: see Rotating client keys and certificates.
- Introspection: an M2M app may introspect its own Management-API tokens.
Related
- OIDC & OAuth 2.0 — client authentication and the token endpoint.
- Authorization — how capabilities and downstream RBAC differ.
- SCIM provisioning — drive provisioning with an M2M token instead of a SCIM token.
SCIM provisioning
Provision users and groups into a SenseCrypt tenant from your identity provider over SCIM 2.0 — the base URL, a bearer token, Users and Groups, filtering, and error handling.
CIBA backchannel
Start a decoupled, no-browser sign-in with CIBA — call the backchannel authentication endpoint, then poll the token endpoint until the user approves on their phone.