skip to content

In the OAuth 2.0 device authorization grant, what does a browserless device display, and where does the user approve it?

level: juniorimportance: should knowfreq 44%

answer

  1. no usable browser on the device
  2. two codes, different audiences
  3. the human moves to a second device
  4. verification_uri opened elsewhere
  5. device_code never leaves the device

basics

~20 s

The device displays a short user_code and a verification_uri. The user opens that URI on a separate phone or laptop, signs in there and enters the code. The device keeps a long device_code the user never sees.

solid answer

~40 s

The device makes a back-channel request to the authorization server's `device_authorization_endpoint` and gets back two codes with different audiences. The `user_code` is short and goes on the device's screen, next to a `verification_uri` the user opens on a second device they already own — a phone or a laptop with a real browser. There the user authenticates and approves the request. The `device_code` is the long opaque handle that stays on the device; it is never shown to anyone, and the device redeems it at the token endpoint. The response also carries `expires_in`, bounding how long both codes live, and optionally `verification_uri_complete`, the same URI with the `user_code` already embedded so it can be rendered as a scannable image.

code

json · 8 lines
json
{
  "device_code": "Cq8x9Vr2m0Tn4Lp7Wz6Jd3Hs1Kf5Bq0Xa",
  "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
}

go deeper

for a junior

Recall the shape: the device shows a short code plus a URI, the user opens that URI on their own phone and enters the code, and the device gets its token afterwards.

for a middle

Explain why there are two codes rather than one, which of them is displayed, and what each member of the device authorization response is for.

for a senior

Show that you know the assumption the whole grant rests on: the approving device cannot talk back, so polling is the only channel and the device must be designed around an outbound-only loop.

for a principal

Frame where this grant belongs in a fleet: which classes of hardware can never run a browser flow, and what that costs in support burden and in codes displayed on screens other people can read.

## The constraint that shapes this grant A ruggedised survey tablet in an excavation trench, a display panel bolted to a wall, a headless build agent on a rack: each runs an OAuth 2.0 **client**, and none of them can reasonably put a login form in front of a person. Either there is no browser at all, or there is no keyboard worth typing a password on. Every browser-based flow assumes the client can send the **resource owner** to the **authorization server**'s authorization endpoint and then receive an authorization response back through a redirect. A device like this can do neither half. RFC 8628 keeps the four-party model intact and moves only the human's part of it onto a *second* device the user already owns. Nothing about the trust model changes: the authorization server still issues, the device is still the client, and the resource server still validates whatever access token comes out of it. ## Step one — the device authorization request The device makes a back-channel form-encoded `POST` to the authorization server's `device_authorization_endpoint`, carrying its `client_id` and, if it wants a narrower grant, a `scope`. No browser is involved, no redirect is registered, and nothing is displayed yet. ## Step two — the response, and its two codes The response is JSON, and the point to fix in memory is that it carries **two** codes aimed at two different readers: | Member | Who it is for | What it does | |---|---|---| | `device_code` | the device only | the opaque handle later redeemed for an access token | | `user_code` | the human | the short string typed on the second device | | `verification_uri` | the human | the page to open on the second device | | `verification_uri_complete` | the human (optional) | the same URI with the `user_code` already in it | | `expires_in` | the device | seconds both codes remain valid | | `interval` | the device | minimum seconds between polls (optional) | A few consequences follow directly from that split: - The `user_code` is short because a human retypes it by hand, often on a small touch keyboard, so it is deliberately low-entropy and usually case-insensitive and hyphenated for readability. - The `device_code` is read by nobody, so it can be long and unguessable. Displaying it would be a straightforward implementation error. - `verification_uri_complete` is optional. A device with a screen can render it as a scannable image so the user does not type at all, and the specification still expects the textual `user_code` to be shown beside it for anyone who cannot scan. - `expires_in` bounds the whole episode. Both codes die together when it elapses. ## Step three — what the human actually does 1. Reads the `user_code` off the device's screen (or scans the complete URI). 2. Opens the `verification_uri` on a phone or laptop with a working browser. 3. Authenticates there, however that authorization server requires. 4. Enters the `user_code` and approves the request the device made. None of this passes through the device. The device is not in the conversation and does not observe it. ## Step four — the device finds out by asking Because it is not in that conversation, the device learns the outcome only by polling the token endpoint with its `device_code`, and is told the request is still pending until the user finishes. That polling contract — its default wait, its back-off signal and its terminal errors — is the part interviewers push hardest on, and it exists precisely because of the next section. ## The assumption underneath the whole grant The defining assumption of RFC 8628 is that **the device on which the user approves has no channel back to the device running the client**. They are two unrelated machines that never speak. There is no callback, no push and no shared session, so a pull loop is the only way the client can ever learn the answer. That assumption is also why the grant survives hostile network placement. The device needs only outbound connectivity to the authorization server; it never has to be reachable, never has to run a listener, and never has to own a redirect destination. The cost is the polling loop, and a short human-typable code whose weakness the authorization server has to compensate for.

  • What is verification_uri_complete for, and why show the user_code next to it anyway?
    It is the `verification_uri` with the `user_code` already embedded, so a device with a screen can render it as a scannable image and the user never types. The textual `user_code` still belongs on screen beside it: a user with no camera, a dirty or cracked screen, or a second device that cannot scan needs the manual path, and the code on screen also lets the user confirm that the page they landed on is talking about their device.
  • Why can the approving phone not simply hand the access token to the device?
    Because the grant assumes the two devices have no channel between them. They are unrelated machines with no shared session, no pairing and no push path; the phone typically has no idea which physical device the `user_code` belongs to. All the approval does is mark the `device_code` as authorized at the authorization server, which is why the device has to poll the token endpoint to find out.

saying these in an interview costs you the question

  • Thinks the device_code is the value the user types in
  • Says the device opens a redirect_uri and receives a code back
  • Assumes the approving phone sends the token to the device
  • Drops the textual user code once a scannable link is shown
  • Treats the user_code itself as the access token