privileged.id

OAuth 2.0

Device authorization grant

Sign-in for TVs, consoles and command-line tools, and the remote phishing attack that abuses the same flow.

Reviewed 6 min read 3 primary sources

The device authorization grant, RFC 8628 (August 2019), lets a device that has no usable browser or keyboard obtain OAuth tokens. The device shows a short code and a URL; the user opens the URL on a phone or computer, signs in there, enters the code, and approves. Meanwhile the device polls the token endpoint until the approval arrives. Smart TVs, media consoles, printers and command-line tools on headless servers all use it.

The same property that makes it convenient, that approval happens on a different device from the one receiving the tokens, is what attackers abuse in device code phishing. Both halves are covered below.

When it applies

RFC 8628 §1 lists the operating requirements: the device is online, can make outbound HTTPS requests, can display or otherwise communicate a URI and a code, and the user has a second device to complete the interaction. The RFC is explicit that it "is not intended to replace browser-based OAuth in native apps on capable devices like smartphones"; those apps should follow RFC 8252 and use the authorization code flow with PKCE.

Device clients are generally public clients: §5.6 notes that anyone with the device can reverse-engineer it and extract any embedded credential.

The flow

Device authorization grant (RFC 8628) with polling Device TV, CLI, console Authorization server Second device user's browser 1. POST /device_authorization 2. device_code, user_code, URI 3. Show URI and code WDJB-MJHT 4. Poll /token (device_code) 5. 400 authorization_pending 6. Sign in, enter user_code 7. Show device details; user approves 8. Poll /token again 9. 200: access token
The device never receives a redirect. It only makes outbound requests: one to start, then repeated polls of the token endpoint.

Device authorization request and response

The device POSTs to the device authorization endpoint (advertised as device_authorization_endpoint in RFC 8414 metadata). The RFC's example values:

POST /device_authorization HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

client_id=1406020730&scope=example_scope

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://example.com/device",
  "verification_uri_complete": "https://example.com/device?user_code=WDJB-MJHT",
  "expires_in": 1800,
  "interval": 5
}
device_code
The secret the device uses to poll. It is never shown to the user. Because no human types it, it SHOULD have very high entropy (§5.2).
user_code
The short code the user types on the second device. It ties the user's approval to this device's session.
verification_uri
Where the user goes. It should be short and memorable, because people type it.
verification_uri_complete
Optional. The URI with the code already embedded, intended for a QR code or NFC. The device MUST still display the user_code (§3.3.1).
expires_in
Lifetime, in seconds, of both codes.
interval
Minimum seconds between polls. If absent, clients MUST use 5.

The RFC adds a usability point that is also a load-control point: the client SHOULD start a device authorization only when the user asks it to, not automatically at app start or whenever a session expires (§3.1).

Polling the token endpoint

While the user is busy on the second device, the device polls with the extension grant type urn:ietf:params:oauth:grant-type:device_code (§3.4):

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code
&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
&client_id=1406020730

HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store

{ "error": "authorization_pending" }

The four device-specific errors (§3.5) define the polling behavior:

ErrorMeaningClient behavior
authorization_pendingThe user has not finished yet.Poll again, waiting at least interval seconds (MUST).
slow_downStill pending, but you are polling too fast.Increase the interval by 5 seconds for this and all later requests (MUST).
access_deniedThe user (or server) refused.Stop polling.
expired_tokenThe device_code has expired.Stop. May start a new request, but SHOULD wait for the user to ask.

Any other error means stop polling (MUST). On a connection timeout the client MUST reduce its polling frequency, and exponential backoff is RECOMMENDED. When the user approves, the next poll returns an ordinary RFC 6749 §5.1 token response.

Designing the user code

A user code has to be short enough to type on a phone and strong enough not to be guessed. RFC 8628 handles the tension with rate limiting: the code SHOULD have enough entropy that, combined with rate limiting and a finite lifetime, brute force is infeasible (§5.1). The RFC's worked example:

  • A base-20 alphabet with no vowels and no digits, BCDFGHJKLMNPQRSTVWXZ, avoids accidental words and needs no modifier keys on a phone keyboard (§6.1).
  • Eight such characters give 208 possibilities, about 34.5 bits. To match the 2-32 success chance the RFC uses as its benchmark, the rate limit and lifetime would need to allow only about 5 guesses.
  • A numeric alternative, nine digits such as 019-450-730, gives 109, about 29.9 bits, and suits locales without A–Z keyboards.
  • Avoid easily confused characters (0/O, 1/l/I); strip dashes and uppercase the input before comparing.

Note what a successful guess gives the attacker: the device gets a grant linked to the attacker's account, the reverse of most token theft. The RFC points out this still matters when the account controls the device or when privacy is at stake (§5.1).

Device code phishing

Section 5.4 of the RFC, "Remote Phishing", describes the attack that matters most in practice:

It is possible for the device flow to be initiated on a device in an attacker's possession. For example, an attacker might send an email instructing the target user to visit the verification URL and enter the user code.

The attack works because nothing in it is fake. The attacker starts a device flow from their own machine against a real authorization server, using a client the server trusts, and receives a genuine user_code. They send the victim a plausible message: a meeting invite, a "verify your account" notice, a support chat. The victim goes to the real verification URL on the real domain, signs in with their real credentials and MFA, and enters the code. The tokens are delivered to the attacker's polling device. Because the victim authenticated at the legitimate server, phishing-resistant authenticators do not stop this: the protection that WebAuthn gives against fake sites does not apply when the site is genuine and only the purpose of the approval is misrepresented.

The RFC's mitigations are on the authorization server's side:

  • It is RECOMMENDED to tell the user they are authorizing a device and to ask them to confirm the device is in their possession.
  • The server SHOULD display information about the device, so a user might notice a software client impersonating a hardware device.
  • With verification_uri_complete the user no longer types the code, so confirming possession is "particularly important". The server SHOULD display the code and ask the user to check it matches what their device shows (§3.3.1).
  • Code lifetime is a trade-off: long enough to be usable, short enough to limit codes sent by email or text. The RFC is candid that a short lifetime does not stop a phisher who generates a fresh code while talking to the victim in real time.

Defenses beyond the RFC, which are deployment choices rather than standard requirements:

  • Turn the grant off where it is not needed. If no client in your tenant needs the device flow, disable it at the authorization server, or allow it only for named clients.
  • Restrict which clients may use it and with which scopes. A TV app needs a narrow scope; it does not need mail, files or admin APIs.
  • Consider location and device signals with care. §5.3 notes that the approving session's signals "are not always relevant to the trustworthiness of the client device", which cuts both ways: policy on the approving browser does not tell you where the polling device is.
  • Watch for it. Device-code sign-ins by users who never use TVs or CLIs, or polling clients from unexpected networks, are worth alerting on.
  • Teach one rule. Only enter a device code that is on a screen in front of you, that you started yourself.

The same family of attacks, getting a user to approve a legitimate authorization prompt for an attacker's benefit, is covered more generally on scopes and consent.

Other risks in the RFC

  • Session spying (§5.5): someone who can see the device's screen can enter the code first. Devices SHOULD consider their environment when displaying codes.
  • Non-visual codes (§5.7): if the code is spoken or sent over Bluetooth, the channel should be reachable only by people near the device (RECOMMENDED).
  • Clickjacking: RFC 9700 §4.16 lists the device authorization endpoint among the pages that need framing protection (CSP frame-ancestors).
  • Malicious authorization server (§5.3): a compromised or malicious device vendor could point the device at the wrong server; the user would see the wrong domain in their browser.

Checklist

  • Use the device grant only for clients that truly cannot run a browser flow.
  • High-entropy device_code; rate-limited, short-lived user_code from an unambiguous alphabet.
  • Approval page names the client and device, asks the user to confirm the device is theirs, and shows the code when verification_uri_complete is used.
  • Clients honor interval, add 5 seconds on slow_down, stop on access_denied and expired_token.
  • Narrow scopes per device client; grant disabled for clients that do not need it.
  • Device-code approvals logged and monitored; users can see and revoke device grants.
  • Refresh tokens for these public clients rotated or sender-constrained (RFC 9700 §2.2.2).

Sources

  1. RFC 8628: OAuth 2.0 Device Authorization Grant, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
  2. RFC 6749: The OAuth 2.0 Authorization Framework, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
  3. RFC 9700: Best Current Practice for OAuth 2.0 Security, IETF. IETF Trust Legal Provisions (brief quotations with attribution).