Device authorization
Use the OAuth 2.0 device authorization flow for CLIs, TVs, and other devices
that cannot conveniently receive a browser callback. The application starts
the flow, the user approves it in a browser, then the application picks up the
token package from /token.
The device flow does not give the application the user's password.
Client requirements
The OAuth client must:
- be active;
- be enabled for the device-code grant;
- use its configured token endpoint authentication method; and
- not be an M7 admin application.
A confidential client may use client_secret_basic, client_secret_post, or
another configured confidential method. A public CLI may use
token_endpoint_auth_method=none and send only client_id. Never embed a
confidential client secret in a device app or browser.
Start device authorization
POST https://sso.user.m7.org/device_authorization
Content-Type: application/x-www-form-urlencoded
Authenticate the client in the same way it authenticates to /token.
For client_secret_jwt or private_key_jwt, the client assertion audience is
the exact device-authorization endpoint URL.
| Field | Required | Description |
|---|---|---|
client_id |
Yes for none and usually present otherwise |
OAuth client identifier. |
scope |
No | Requested space-delimited scopes. Omit it to request the client's configured allowed scopes. |
aud |
No | Requested audience. It may occur more than once to request multiple audiences. Every requested audience must be allowed for the client. |
nonce |
No | Carries into an ID token when openid is granted. |
fingerprint |
No | 64-character hexadecimal M7 credential binding. |
access_expires |
No | Requested access-token lifetime in seconds. |
refresh_expires |
No | Requested refresh-token lifetime in seconds. |
claims |
No | JSON object for claims requested for both token classes. |
access_claims |
No | JSON object for requested access-token claims. |
refresh_claims |
No | JSON object for requested refresh-token claims. |
Example for a confidential client:
curl --fail-with-body --silent --show-error \
--request POST 'https://sso.user.m7.org/device_authorization' \
--user 'CLIENT_ID:CLIENT_SECRET' \
--data-urlencode 'scope=openid profile email offline_access' \
--data-urlencode 'aud=https://api.example.com'
Authorization response
M7 returns a pending authorization with a 10-minute lifetime:
{
"device_code": "DEVICE_CODE",
"user_code": "ABCD-EFGH",
"verification_uri": "https://sso.user.m7.org/device_login",
"verification_uri_complete": "https://sso.user.m7.org/device_login?user_code=ABCD-EFGH",
"expires_in": 600,
"interval": 5
}
device_code is a secret. Keep it out of the user-facing display, logs, and
browser history. Show the user verification_uri_complete as a clickable
link, and also show user_code for manual entry.
Store the device authorization server-side for confidential clients. Store at least the device code, expiry, interval, and local workflow state.
User approval
The user opens verification_uri_complete in a normal browser, or visits
https://sso.user.m7.org/device_login and enters user_code. M7 collects the
necessary sign-in proof and approval. Your application does not call the
browser form-processing endpoints and must not collect M7 credentials.
Poll for the token package
Poll /token no more frequently than the response's interval. Send the
same OAuth client authentication used to start the flow:
curl --silent --show-error \
--request POST 'https://sso.user.m7.org/token' \
--user 'CLIENT_ID:CLIENT_SECRET' \
--data-urlencode 'grant_type=device_code' \
--data-urlencode 'device_code=DEVICE_CODE'
For a public client, omit the secret and add client_id=CLIENT_ID to the form
body. M7 also accepts the standard grant value
urn:ietf:params:oauth:grant-type:device_code.
Before approval, M7 returns:
{
"error": "authorization_pending",
"error_description": "device authorization is still pending"
}
After approval, the response is the token package described in
Token endpoint. The successful pickup consumes
the device code. Stop polling on success, expiry, invalid_grant, or any
other terminal failure.
Security and UX rules
- Follow
interval; do not aggressively poll. - Never display or ask the user to type
device_code. - Never put a client secret in the device UI, QR code, or browser code.
- Use an application-owned secure store for the returned refresh package.
- Use the supported bearer profile for new integrations. Do not infer public DPoP support from accepted headers or credential metadata; see DPoP status.