Device Code Token Pickup

This document describes what another site / RP sends to SSO after a device authorization has already been approved.

Purpose

The RP already has:

  • device_code
  • its own OAuth client credentials or public client_id

The user has already approved the request at:

  • https://sso.user.m7.org/device_login

At that point, the RP redeems the device_code at SSO's token endpoint.

Endpoint

POST https://sso.user.m7.org/token

Content type:

application/x-www-form-urlencoded

Required form fields

Always send:

  • grant_type=device_code
  • device_code=<DEVICE_CODE>

Client authentication

The RP must authenticate the OAuth client the same way it does for normal /token calls.

Confidential client

Send one of:

  • Authorization: Basic base64(client_id:client_secret)
  • client_id=<CLIENT_ID> and client_secret=<CLIENT_SECRET>

Use whichever token_endpoint_auth_method is configured for that OAuth client.

Public client

If the OAuth client is configured with:

  • token_endpoint_auth_method = none

then send:

  • client_id=<CLIENT_ID>

and do not send a client secret.

What not to send

Do not send:

  • user_code
  • username / password
  • browser cookies
  • sso_session_id
  • session_key

Those are not part of the pickup request.

Where this should be called from

Confidential client

Call /token from the RP backend, not browser JavaScript.

Reason:

  • the browser must not hold the client secret

Public client

A CLI or other public device client may call /token directly if it is registered as a public client.

Example request

client_secret_post

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>

client_secret_basic

POST /token
Authorization: Basic <BASE64_CLIENT_ID_COLON_SECRET>
Content-Type: application/x-www-form-urlencoded

grant_type=device_code&device_code=<DEVICE_CODE>

Public client

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=device_code&device_code=<DEVICE_CODE>&client_id=<CLIENT_ID>

Example curl

curl -sS https://sso.user.m7.org/token \
  -d grant_type=device_code \
  -d device_code='<DEVICE_CODE>' \
  -d client_id='<CLIENT_ID>' \
  -d client_secret='<CLIENT_SECRET>'

Successful response

On success, SSO returns the pre-issued token package for that device authorization.

Current shape:

  • token_type
  • access_token
  • expires_in
  • refresh_token
  • refresh_expires_in
  • binding_chain
  • binding_link
  • scope

Example:

{
  "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"
}

One-time pickup behavior

device_code pickup is one-time.

After successful /token redemption:

  • the device authorization is consumed
  • the same device_code should not be redeemed again

Common failure cases

User has not approved yet

Expected error:

  • authorization_pending

Wrong client tries to redeem

Expected error:

  • invalid_grant

Expired or already-consumed device code

Expected error:

  • invalid_grant

Client is not allowed to use device flow

Expected error:

  • unauthorized_client

After pickup

After the RP receives the token package, it should use the returned refresh material for normal refresh flow.

That means later refresh requests go back through the normal /token refresh path using:

  • refresh_token
  • binding_chain
  • binding_link

and any other normal refresh requirements that apply to that client.

1