Device flow and token exchange

OAuth, OIDC & JWT · Advanced · 12 min

Input-constrained devices and service chains need flows beyond the redirect-based one.

Device authorization grant (RFC 8628) serves devices without a usable browser or keyboard: smart TVs, CLIs, IoT devices.

  • The device POSTs to the device authorization endpoint and gets a device_code, a short user_code, a verification_uri (often also verification_uri_complete for QR codes) and a polling interval.
  • It shows "Visit example.com/device and enter WDJB-MJHT".
  • The user completes login and consent on their phone or laptop.
  • Meanwhile the device polls the token endpoint with grant_type=urn:ietf:params:oauth:grant-type:device_code, receiving authorization_pending until the user finishes, and slow_down if it polls too fast.

gh auth login, az login --use-device-code and many streaming apps use it. It is phishable (attackers can send victims a code to approve), so show the client name clearly on the approval screen and keep codes short-lived.

Token exchange (RFC 8693) lets a service trade one token for another: grant_type=urn:ietf:params:oauth:grant-type:token-exchange with a subject_token. Uses include:

  • Down-scoping a user's token for a specific downstream API (new audience), instead of forwarding the original broad token.
  • Delegation chains where the new token records the acting service in an act claim.
  • Workload identity federation: exchanging a Kubernetes or GitHub Actions OIDC token for a cloud access token.

Example

# 1. Device asks for codes
curl -s https://auth.example.com/oauth/device/code \
  -d client_id=tv-app -d scope="openid profile"
# {"device_code":"GmRhm...","user_code":"WDJB-MJHT",
#  "verification_uri":"https://example.com/device","expires_in":900,"interval":5}

# 2. Poll every `interval` seconds until the user approves
curl -s https://auth.example.com/oauth/token \
  -d grant_type=urn:ietf:params:oauth:grant-type:device_code \
  -d device_code=GmRhm... -d client_id=tv-app
# {"error":"authorization_pending"}  ...later...  {"access_token":"...", ...}

# Token exchange: get a narrower token for a downstream API
curl -s https://auth.example.com/oauth/token -u "orders-api:$SECRET" \
  -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
  -d subject_token="$USER_ACCESS_TOKEN" \
  -d subject_token_type=urn:ietf:params:oauth:token-type:access_token \
  -d audience=https://inventory.example.com

Remember

  • Device flow: show a user code, the user approves elsewhere, the device polls
  • Honour interval and slow_down when polling
  • Token exchange swaps a token for one with a narrower audience or delegation

Watch out for

  • Forwarding a user's broad access token to every downstream service instead of exchanging it.

Check yourself

  1. What does a device receive from the token endpoint while the user has not yet approved?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.rfc-editor.org/rfc/rfc8628

Builds on

Unlocks

Nothing builds on this yet.