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.