Device Code RP Setup
This document describes the full device-code flow for another site / RP that wants to offer offline access.
Example user story:
- user is on
bigfs.m7.org - user clicks
Get offline access bigfsstarts the device flow with SSO- user approves on
sso.user.m7.org bigfsredeems the device code and stores the returned token package
What this flow is for
Use this when:
- the RP wants long-lived API access outside the normal browser session
- the RP wants to issue a token package after the user explicitly approves it
- the RP does not want to push browser login credentials through the RP itself
Client setup
The OAuth client used for this flow must:
- be active
- allow
device_codeingrant_types
Typical confidential RP setup:
token_endpoint_auth_method = client_secret_postgrant_typesincludesdevice_code
Typical public CLI setup:
token_endpoint_auth_method = nonegrant_typesincludesdevice_code
High-level flow
- User clicks
Get offline accesson the RP. - RP backend calls SSO
POST /device_authorization. - SSO returns
device_code,user_code, and verification URLs. - RP stores
device_code. - RP shows the user where to approve.
- User goes to
sso.user.m7.org/device_login. - User enters the code and approves.
- RP redeems
device_codeatPOST /token. - SSO returns the token package.
- RP stores the returned refresh material for later use.
Step 1: User clicks on the RP
Example UI:
- button text:
Get offline access - helper text:
Approve a device token for CLI or background uploads
At this point, the RP should create a server-side action that starts the flow.
For a confidential client, do this on the RP backend, not in browser JavaScript.
Step 2: RP calls device authorization
Endpoint:
POST https://sso.user.m7.org/device_authorization
Content type:
application/x-www-form-urlencoded
Required parameters
Always send:
client_id
Usually send:
scope
Optional:
audMay be sent once for a single audience, or repeated to request multiple audiences.
Confidential client auth
Send one of:
client_id+client_secretAuthorization: Basic ...
depending on the configured token_endpoint_auth_method.
Public client auth
If the client is public:
- send
client_id - do not send a secret
Example request
POST /device_authorization
Content-Type: application/x-www-form-urlencoded
client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>&scope=openid%20profile%20email%20groups%20offline_access
Multi-audience example:
POST /device_authorization
Content-Type: application/x-www-form-urlencoded
client_id=<CLIENT_ID>&aud=https%3A%2F%2Fuser.m7.org%2Fapi%2Fv2%2F&aud=https%3A%2F%2Ffiles.m7.org%2Fapi%2Fv1%2F
Step 3: SSO returns device authorization data
Current response shape:
{
"device_code": "<DEVICE_CODE>",
"user_code": "<USER_CODE>",
"verification_uri": "https://sso.user.m7.org/device_login",
"verification_uri_complete": "https://sso.user.m7.org/device_login?user_code=<USER_CODE>",
"expires_in": 600,
"interval": 5
}
Step 4: RP stores device state
The RP should store at least:
device_codeuser_codeverification_uriverification_uri_completeexpires_ininterval- local status like
pending
For a confidential RP:
- keep
device_codeon the server side - do not rely on the browser to preserve it
Step 5: RP tells the user how to approve
The RP should show:
- the
user_code - a clickable approval URL
Best UX:
- show
verification_uri_completeas a button - also show the plain
user_codein case the user types it manually
Example text:
Go to sso.user.m7.org/device_login and enter code ABCD-EFGH
Step 6: User approves on SSO
User goes to:
https://sso.user.m7.org/device_login
Current SSO approval behavior:
- user enters
user_code - user enters password
- email may be left blank if the user already has an SSO session and SSO can resolve the account from that session
The RP does not receive the password.
Step 7: RP waits for approval
The RP has two reasonable choices:
- poll
/token - wait for the user to click a local
Continuebutton, then try/token
Current SSO behavior supports polling.
If the approval has not happened yet, /token returns:
authorization_pending
Use the returned interval from device authorization as the minimum polling interval.
Step 8: RP redeems the device code
Endpoint:
POST https://sso.user.m7.org/token
Required form fields:
grant_type=device_codedevice_code=<DEVICE_CODE>
Confidential clients also send:
client_idclient_secret
or use Basic auth.
Example request
POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=device_code&device_code=<DEVICE_CODE>&client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>
Step 9: SSO returns the token package
On success, SSO returns:
{
"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 email groups offline_access"
}
This is the one-time pickup response for that device_code.
Step 10: RP stores the returned token material
The RP should store:
refresh_tokenrefresh_expires_inbinding_chainbinding_link- granted
scope
The access token may be used immediately, but the important long-lived material is the refresh-side package.
After initial pickup
Later refreshes go through the normal /token refresh path, not the device-code path.
That means the RP later sends:
grant_type=refresh_tokenrefresh_tokenbinding_chainbinding_link
plus normal client authentication for that client.
One-time behavior
The device_code pickup is one-time.
After a successful /token response:
- the device authorization is consumed
- the same
device_codeshould not be expected to work again
Recommended RP state machine
Suggested local states:
pendingapproved_waiting_pickupredeemedexpiredfailed
Suggested transitions:
- after
/device_authorization->pending - after user approval is expected -> still
pendinglocally, until/tokensucceeds - after successful
/token->redeemed - after timeout / expiry ->
expired
Common errors
Device not approved yet
Response:
{
"error": "authorization_pending",
"error_description": "device authorization is still pending"
}
Wrong client redeems the code
Response:
invalid_grant
Expired or already-used device code
Response:
invalid_grant
Client not allowed for device flow
Response:
unauthorized_client
Simple RP implementation recipe
- Add an RP backend action called something like
startOfflineAccess(). - In that action, call
POST /device_authorization. - Save the returned
device_codeserver-side. - Show
user_codeandverification_uri_completeto the user. - Poll
POST /tokeneveryintervalseconds. - Stop polling when SSO returns the token package.
- Store the returned refresh-side material.
- Use normal refresh flow from then on.
Confidential RP rule
If the RP is a confidential client:
- browser starts the action
- RP backend talks to SSO
- browser shows status
- RP backend redeems the token
The browser should not hold the client secret and should not be the final /token caller.