Token endpoint
POST https://sso.user.m7.org/token
Content-Type: application/x-www-form-urlencoded
Use the token endpoint to exchange authorization codes and device codes,
refresh an existing package, or obtain a service token with
client_credentials. It accepts only POST.
Client authentication is always tied to the OAuth client's configured
token_endpoint_auth_method. A client must also be enabled for the requested
grant type.
Client authentication
| Method | Request contract |
|---|---|
none |
Send client_id in the form body. Suitable for public clients. It cannot use client_credentials. |
client_secret_basic |
Send HTTP Authorization: Basic base64(client_id:client_secret). |
client_secret_post |
Send client_id and client_secret in the form body. |
client_secret_jwt |
Send client_id, client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer, and a signed client_assertion. |
private_key_jwt |
Send the same assertion fields. The client must have an inline jwks or HTTPS jwks_uri configured. |
For JWT client assertions, iss and sub must equal the client_id, aud
must equal the exact token endpoint URL, and the assertion must contain a
current iat, unexpired exp, and unique jti. M7 rejects replayed and stale
assertions. Supported private_key_jwt signing algorithms are RSA (RS256,
RS384, RS512) and ES256; configure the intended algorithm on the client.
client_secret_jwt uses a configured HMAC algorithm: HS256, HS384, or
HS512.
Authorization-code exchange
Send these form fields after receiving a code at the registered callback:
| Field | Required | Notes |
|---|---|---|
grant_type |
Yes | authorization_code |
code |
Yes | The authorization code from /authorize. It is single-use. |
redirect_uri |
Yes | Exactly the same registered URI used in the authorization request. |
code_verifier |
Yes | The original PKCE verifier. M7 verifies its SHA-256 challenge. |
fingerprint |
No | A 64-character hexadecimal M7 credential binding, when the client integration uses one. |
Authenticate the client as described above. See the complete browser flow in Quickstart.
Refresh-token exchange
The normal refresh request sends the complete refresh package, not just the refresh token:
| Field | Required | Notes |
|---|---|---|
grant_type |
Yes | refresh_token |
refresh_token |
Yes | The refresh credential. M7 also accepts the legacy alias token. |
binding_chain |
Required for an M7 refresh package | Save and send the value returned with the refresh token. |
binding_link |
Required for an M7 refresh package | Save and send the value returned with the refresh token. |
fingerprint |
Conditional | Required when the existing lineage is fingerprint-bound. Exactly 64 hexadecimal characters. |
new_fingerprint |
No | A replacement binding fingerprint. When fingerprint is supplied and new_fingerprint is omitted, M7 keeps the same value. |
access_expires |
No | Requests the next access-token lifetime when allowed by policy. |
access_claims |
No | Requests allowed access-token claims when allowed by policy. |
refresh_mode |
No | M7 lifecycle extension; normally omit it. See below. |
The refresh token must belong to the authenticated client_id. Persist the
full replacement package before removing the old package. If the requested
refresh cannot proceed, keep the prior package unless the service has returned
a confirmed replacement or terminal error that makes it unusable.
Refresh lifecycle extension
Without refresh_mode, M7 currently uses strict_rotation. Supported modes
are static, strict_rotation, grace_rotation, ack, and
ack_supersede_pending.
Most applications should omit this parameter. ACK modes require durable, atomic persistence and a recovery procedure because the returned package is initially pending. See Token acknowledgement.
Client credentials
Use this grant for a machine application acting as itself, never as an end
user. The client must be confidential and be enabled for client_credentials.
| Field | Required | Notes |
|---|---|---|
grant_type |
Yes | client_credentials |
aud |
Yes | One requested audience or resource identifier. |
scope |
No | Requested space-delimited scope string, subject to client policy. |
access_expires |
No | Requested access lifetime. expires_in and expires are accepted aliases. |
claims |
No | Requested JSON claims object. payload is an accepted alias. |
fingerprint |
No | 64-character hexadecimal M7 binding. |
label |
No | A management label for the issued record. |
Example:
curl --fail-with-body --silent --show-error \
--request POST 'https://sso.user.m7.org/token' \
--user 'CLIENT_ID:CLIENT_SECRET' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'aud=https://api.example.com' \
--data-urlencode 'scope=file.read file.write' \
--data-urlencode 'access_expires=900'
This flow returns an access token only; it does not issue a refresh token. A
client using token_endpoint_auth_method=none is rejected for this grant.
Device-code exchange
Use this only after obtaining device_code from
/device_authorization:
| Field | Required | Notes |
|---|---|---|
grant_type |
Yes | device_code. The standard urn:ietf:params:oauth:grant-type:device_code identifier is also accepted. |
device_code |
Yes | Secret device code returned by /device_authorization. |
Authenticate the same client that initiated the device flow. If the user has
not approved yet, the response is authorization_pending. Observe the
response's interval; do not poll more frequently. A device code is consumed
after successful pickup.
Token responses
The response varies by grant and client policy. A user-authorized success typically contains:
{
"token_type": "bearer",
"access_token": "ACCESS_TOKEN",
"expires_in": 900,
"refresh_token": "REFRESH_TOKEN",
"refresh_expires_in": 2592000,
"binding_chain": "BINDING_CHAIN",
"binding_link": "BINDING_LINK",
"scope": "openid profile",
"id_token": "ID_TOKEN"
}
id_token is returned when openid was granted. An access token from
client_credentials does not have a user refresh package. Treat every token
and every binding or activation field as a secret.
The response above is the OAuth token-package envelope, not the decoded access token payload. See Access-token formats for the consumer, tenant-member, and OAuth-application JWT claim profiles.
Current examples use token_type: "bearer", which is the supported public
authorization profile. Do not opt a new integration into DPoP based on
accepted headers or token confirmation data. An already provisioned
DPoP-bound lineage must follow its compatibility profile; see
DPoP status.
Errors
Errors use OAuth JSON fields such as invalid_request, invalid_client,
invalid_grant, unauthorized_client, invalid_scope, and
unsupported_grant_type. Do not branch on prose in error_description.