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.
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 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:
| Error | Meaning | Client behavior |
|---|---|---|
authorization_pending | The user has not finished yet. | Poll again, waiting at least interval seconds (MUST). |
slow_down | Still pending, but you are polling too fast. | Increase the interval by 5 seconds for this and all later requests (MUST). |
access_denied | The user (or server) refused. | Stop polling. |
expired_token | The 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_completethe 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-liveduser_codefrom 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_completeis used. - Clients honor
interval, add 5 seconds onslow_down, stop onaccess_deniedandexpired_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
- RFC 8628: OAuth 2.0 Device Authorization Grant, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 6749: The OAuth 2.0 Authorization Framework, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 9700: Best Current Practice for OAuth 2.0 Security, IETF. IETF Trust Legal Provisions (brief quotations with attribution).