ndovuoauth2

FLOW DEEP DIVE · RFC 8628 · AUGUST 2019

The device flow

The OAuth2 device authorization grant (RFC 8628) on the wire: the two-screen flow, the exact polling rules, slow_down done right, the user-code format, and the remote-phishing problem.

October 26, 2026input-constrained devicesThe code flow for comparison
01 · What it is and when it reaches for it

RFC 8628 (Denniss, Bradley, Jones, Tschofenig) exists for devices that "lack a browser to perform a user-agent-based authorization or are input constrained" - smart TVs, streaming sticks, consoles, printers, and the quiet workhorse use: CLI tools on machines you do not want to type secrets into (gcloud auth login, gh auth login, Docker, SSH sessions, containers).

The idea: the device cannot render a login page, but the user has a phone in reach. So the flow splits across two screens. The device asks the authorization server for a short human-typeable code and shows it; the user opens a URL on a different device, signs in there, and types the code; meanwhile the device politely polls the token endpoint until the human finishes. The spec's operating requirements (section 1) are modest: the device is online, can make outbound HTTPS, can display a URI plus code, and the user has a secondary device. It is explicitly not for capable native apps - those get the code flow (RFC 8252).

02 · The flow
Device flow: the device, the authorization server, and the user approving on a second deviceDevice (TV, CLI)Auth serverUser's phonepublic clientreal browser + keyboard1. POST device_authorization_endpoint2. device_code, user_code,verification_uri, expires_in, interval3. screen shows: "go to example.com/device,enter code WDJB-MJHT"4. user visits, signs in,enters the user_code, approves5. poll: POST /token (device_code)400 authorization_pendingrepeat at interval (5s default), backing off on slow_down6. poll (user has approved)200 access_token (+ refresh_token)steps 1-2 and polling are back channel (direct TLS); step 4 happens in the user's own browser, on their own device

Note what the device never sees: credentials, session cookies, or the consent click. Its only input is a code the user could also read over your shoulder - which is why the security analysis centres on that code.

03 · The wire
Step 1 - the device authorization request (3.1)
POST /device_authorization HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

client_id=1406020730
scope=example_scope

A separate endpoint from the authorization endpoint - the browser never visits it. TLS is a MUST, and the spec asks clients to start the flow only "when prompted by the user and not automatically, such as when the app starts" (3.1).

Step 2 - the device authorization response (3.2)
HTTP/1.1 200 OK
Content-Type: application/json

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

The example values here are RFC 8628 section 3.2's own published example - the same WDJB-MJHT user code appears in the RFC.

FieldStatusRole
device_codeREQUIREDThe machine's half of the pair - never displayed to the user, high entropy (section 5.2)
user_codeREQUIREDThe human's half - short, typeable, shown on the TV screen
verification_uriREQUIRED"Should be short and easy to remember as end users will be asked to manually type it"
verification_uri_completeOPTIONALURL with the code baked in - designed for QR codes and NFC (3.3.1)
expires_inREQUIREDLifetime of both codes; the whole session dies when it lapses
intervalOPTIONALMinimum seconds between polls; clients MUST default to 5 when absent (3.2)
Steps 5-6 - polling the token endpoint (3.4, 3.5)
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

A registered extension grant - the grant_type is the URNurn:ietf:params:oauth:grant-type:device_code (section 7.2). This is the one OAuth grant where polling is the protocol: "it is expected for the client to try the access token request repeatedly in a polling fashion based on the error code in the response" (3.4).

04 · The polling rules, exactly
Response (3.5)MeaningClient behavior
authorization_pendingUser has not finished on the second device yetKeep polling - but "before each new request, the client MUST wait at least" interval seconds (5 if none was given)
slow_downYou are polling too fastThe interval "MUST be increased by 5 seconds for this and all subsequent requests" - a cumulative, permanent bump. Three slow_downs means interval +15s for the rest of the session. (Explainers that say "wait 5 more seconds once" have it wrong.)
access_deniedThe user declinedStop. A normal outcome; surface it as one
expired_tokenThe device session lapsedStop; MAY restart the flow, but SHOULD wait for user intent first
anything elseA real errorThe client MUST stop polling

One more rule from 3.5 worth quoting: on connection timeouts the client "MUST unilaterally reduce their polling frequency", with exponential backoff RECOMMENDED. Polling politely is the contract that keeps authorization servers willing to offer this grant.

05 · The user code, designed for thumbs

Section 6.1 is a small masterclass in designing for humans. Recommendation: letters only, case-insensitive, digits avoided, and vowels removed - the base-20 charset BCDFGHJKLMNPQRSTVWXZ - so no entry can spell a real word. Format with dashes (WDJB-MJHT); the server strips them, uppercases input, and drops out-of-charset characters on the way in. An 8-character base-20 code carries roughly 34.5 bits of entropy per the spec's own math in section 5.1 - deliberately low, because a human types it, and safe only because the server rate-limits guesses.

Two myths to retire. There is no "20-bit entropy requirement" anywhere in the RFC - section 5.1 leaves length and entropy to the authorization server's discretion. And the security pairing is asymmetric by design: the user_code is weak because humans type it, while thedevice_code (never displayed) "SHOULD" be very high entropy (section 5.2). The surprise in 5.1: brute-forcing a user_code wins an attacker an authorization grant on their own account, attached to your device - the mirror image of a stolen bearer token, and the reason the flow survives short codes.

For QR-capable devices, verification_uri_complete skips typing entirely (3.3.1) - but the server SHOULD then still display the user_code and ask the user to confirm it matches the device's screen. That confirmation step is the tripwire against the attack below.

06 · How it goes wrong
FailureWhat happensThe fix
Remote phishing (5.4)Attacker starts the flow on their device, then emails the victim: "visit this URL and enter this code." The victim's approval lands on the attacker's deviceThe spec's mitigations: say what is being authorized, display device info, keep user_code lifetimes short - and treat the code-confirmation step as non-optional in your own implementation (the spec calls it "particularly important", with a SHOULD on the server to ask users to verify the code)
User-code brute force (5.1)Short codes invite guessing at scaleRate-limit per client and per account; short expires_in; the math in 5.1 shows ~5 allowed guesses keeps success at 2^-32
Rude pollingFixed tight loops ignore interval and slow_down, tripping rate limits and locking devices outHonour interval (5s default), apply slow_down cumulatively, back off on timeouts (3.5)
Device treated as confidentialEmbedded credentials are extractable by the device owner - "users in possession of the device can reverse-engineer it and extract the credentials" (5.6)Model it as the public client it is: refresh tokens sender-constrained or rotated (RFC 9700 §2.2.2)
Shoulder surfing / screen capture (5.5)The code on a lounge TV is readable by anyone in the room, and the TV's own software may spy on the sessionShort lifetimes, one code per session, and the consent screen showing what was requested
07 · Primary sources