Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Device Authorization Flow

The device authorization flow (RFC 8628) enables devices that cannot display a URL or receive a redirect — TVs, CLI tools, smart appliances, IoT sensors — to authorize a user without a browser on the device itself.


Overview

  1. The device requests a code pair from ahdapa.
  2. The device displays a short user code and the verification URL to the user.
  3. The user visits the URL on a separate device (phone, laptop) and enters the code to grant access.
  4. The device polls the token endpoint until the user approves or the code expires.

Step 1 — Device authorization request

POST /device_authorization with the client_id and requested scope. Client authentication follows the same rules as the token endpoint: client_secret_basic, client_secret_post, client_secret_jwt, private_key_jwt, tls_client_auth, self_signed_tls_client_auth, none (public clients), or kerberos_client_auth (SPNEGO/Negotiate).

curl -s -X POST https://idp.example.com/device_authorization \
  -u "CLIENT_ID:CLIENT_SECRET" \
  -d "scope=openid profile email offline_access"

Successful response — 200 OK:

{
  "device_code": "BASE64URL_DEVICE_CODE",
  "user_code": "BCDF-GHJK",
  "verification_uri": "https://idp.example.com/device",
  "verification_uri_complete": "https://idp.example.com/device?user_code=BCDF-GHJK",
  "expires_in": 1800,
  "interval": 5
}
FieldDescription
device_codeOpaque code used to poll the token endpoint. Keep this secret on the device.
user_code8-character code (consonants only, hyphen-separated) displayed to the user. Case-insensitive.
verification_uriThe URL the user must visit. Always <issuer>/device.
verification_uri_completeA pre-filled URL that includes the user code; suitable for QR codes.
expires_inSeconds until the device code expires (1800 = 30 minutes).
intervalMinimum number of seconds to wait between polling attempts (5 seconds).

Machine clients using kerberos_client_auth

An enrolled machine (e.g. an SSSD host) can authenticate to /device_authorization using its Kerberos host keytab instead of a client secret. The machine presents its AP-REQ in the standard HTTP Negotiate header:

# The machine acquires a Kerberos ticket for the IdP's HTTP service principal.
kinit -k -t /etc/krb5.keytab host/node1.example.com@EXAMPLE.COM

curl -s -X POST https://idp.example.com/device_authorization \
  --negotiate -u: \
  -d "client_id=TEMPLATE_CLIENT_ID" \
  -d "scope=openid offline_access"

The client must be registered with:

  • token_endpoint_auth_method = "kerberos_client_auth"
  • grant_types containing urn:ietf:params:oauth:grant-type:device_code
  • The desired scopes including offline_access if a refresh token is wanted

The server verifies the SPNEGO token, matches the machine principal against the registered kerberos_principal or kerberos_principal_pattern, and issues the device code pair exactly as for a secret-authenticated client.

Use case: A headless machine (IoT gateway, data-collection appliance) holds a Kerberos keytab and needs to act on behalf of an authorised user. The machine itself has no browser and no client secret. It authenticates the OAuth2 client registration with Kerberos, then prompts a user to visit the verification URL on a phone or laptop to grant the specific user-level authorization.

Polling the token endpoint after the user approves also uses Kerberos:

curl -s -X POST https://idp.example.com/token \
  --negotiate -u: \
  -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
  -d "client_id=TEMPLATE_CLIENT_ID" \
  -d "device_code=BASE64URL_DEVICE_CODE"

See Kerberos client authentication for registration requirements and template-client patterns.


Step 2 — User interaction

Show the user the verification_uri and user_code:

To authorize this device, visit:
  https://idp.example.com/device

And enter the code:
  BCDF-GHJK

Or scan this QR code: [QR for verification_uri_complete]

This code expires in 30 minutes.

The user visits the URL on a separate authenticated device (or authenticates on arrival if they have no session), enters the code, and approves or denies access. The server records their decision in the database.


Step 3 — Polling the token endpoint

While the user is authorizing, the device polls POST /token at the rate specified by interval. Authenticate the client the same way as in step 1.

curl -s -X POST https://idp.example.com/token \
  -u "CLIENT_ID:CLIENT_SECRET" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
  -d "device_code=BASE64URL_DEVICE_CODE"

Poll responses before the user acts:

{"error": "authorization_pending"}

If you poll too quickly:

{"error": "slow_down"}

When slow_down is received, increase the polling interval by at least 5 seconds for all subsequent attempts.


Step 4 — Successful authorization

Once the user approves, the next poll returns tokens:

{
  "access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K0pXVCIsImtpZCI6IkFCQ0QxMjM0In0...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "BASE64URL_ENCRYPTED_REFRESH_TOKEN",
  "id_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkFCQ0QxMjM0In0...",
  "scope": "openid profile email offline_access"
}

The device code is consumed on first successful redemption. Subsequent polls for the same device_code return invalid_grant.


Step 5 — Expiry and error handling

Error codeMeaningWhat to do
authorization_pendingUser has not yet acted.Keep polling at the specified interval.
slow_downPolling too fast.Increase interval by ≥ 5 s, then retry.
access_deniedUser denied access.Inform the user; device cannot proceed.
expired_tokenThe device code expired (30 minutes).Restart from step 1 with a new device authorization request.
invalid_grantCode already used or not found.Restart from step 1.

Using refresh tokens

The device flow issues a refresh token when offline_access is in the granted scope. Use it to renew the access token without requiring the user to re-authorize the device. See Refresh Tokens.