skip to content

Why is a device-grant user_code kept short, and what must the authorization server do about the guessing risk?

level: seniorimportance: should knowfreq 38%

answer

  1. two codes, opposite audiences
  2. typability is bought with entropy
  3. eight characters, twenty symbols
  4. reachability is all a guesser needs
  5. rate limiting sits on the server

basics

~20 s

The user_code is retyped by a human on a second device, so it is short, case-insensitive and drawn from a reduced character set. That costs entropy, and the compensation sits with the authorization server: rate-limit attempts at the verification page and keep the code short-lived and one-time.

solid answer

~40 s

The two codes in a device authorization response have opposite design pressures. The `device_code` is read only by software, so it can be long and unguessable. The `user_code` is retyped by a person, often on a small touch keyboard in poor conditions, so it is short, usually case-insensitive, drawn from a reduced alphabet that avoids look-alike characters, and often hyphenated for readability. RFC 8628 works the arithmetic itself: eight characters over a twenty-character alphabet is 20^8, roughly 34.5 bits — nowhere near a random secret. Because anyone who can reach the `verification_uri` can try codes without knowing which device they belong to, the mitigation cannot live on the device. The authorization server rate-limits attempts, retires a code after a few failures, and keeps `expires_in` short so the guessing window stays small.

code

json · 7 lines
json
{
  "device_code": "Cq8x9Vr2m0Tn4Lp7Wz6Jd3Hs1Kf5Bq0Xa",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://example.com/device",
  "expires_in": 900,
  "interval": 5
}

go deeper

for a junior

Know that the code the user types is short on purpose, and that short means guessable, so something else has to make guessing impractical.

for a middle

Explain the asymmetry between the two codes and name the concrete usability choices — short, case-insensitive, reduced alphabet, hyphenated — that cost entropy.

for a senior

Show where the control actually lives: rate limiting at the verification page, a short lifetime, one-time codes, and no oracle in the error messages.

for a principal

Set the policy: how long a code may live given your hardware and your users, what attempt budget you will fund, and how you would detect an enumeration campaign across the whole estate.

## Two codes, opposite requirements A device authorization response carries a `device_code` and a `user_code`, and almost everything interesting about their design follows from who reads each one. - The `device_code` is consumed only by software. Nobody types it, nobody reads it aloud, and it travels only on a back-channel request. It can therefore be as long and as random as the authorization server likes, and it should be. - The `user_code` is read off one screen by a human and typed into another, sometimes with gloved hands on a small touch keyboard, sometimes read aloud across a room. Every property that makes it usable costs entropy. That asymmetry is deliberate and it is the whole subject of this question. ## What usability costs Making a code typable pushes in one direction only: - **Short.** Eight characters is about the ceiling before error rates climb. - **Case-insensitive.** Doubling the alphabet by mixing case invites transcription errors, so the case distinction is usually thrown away. - **A reduced alphabet.** Characters that look alike on a low-quality screen are removed, and vowels are often dropped so the generator cannot accidentally produce a word. - **Hyphenated.** A separator makes the code readable in chunks, and the server is expected to accept the code whether or not the user types the hyphen. RFC 8628 does the arithmetic in the open: a code of eight characters over a twenty-character alphabet has 20^8 possible values, roughly **34.5 bits** of entropy. A random secret intended to resist offline attack has three or four times that. The specification is not apologising for the number — it is showing why the mitigation has to live somewhere else. ## Why the guesser does not need the device The key property is that guessing this code requires nothing but reachability. An attacker does not need to be near the device, does not need to know that any particular device is waiting, and does not need to intercept anything. They simply submit codes at the verification page and see whether one is recognised. Every outstanding `user_code` in the whole system is a live target simultaneously, so the effective search is against the *set* of currently valid codes, not against one. ## Where the compensation has to sit | Party | What it can do about guessing | |---|---| | the device | nothing — it never sees an attempt | | the approving browser | nothing — an attacker does not have to use one | | the authorization server | everything — it counts and refuses attempts | The measures are the obvious ones, and RFC 8628 calls for them directly: 1. **Rate-limit the verification page.** Attempts per source and per session are capped, hard, so the number of guesses possible inside a code's lifetime stays in the handful. This is the primary control; everything else narrows the window it has to cover. 2. **Keep `expires_in` short.** A code that lives thirty minutes is a target for thirty minutes. The lifetime should be the time a real user plausibly needs, not a generous round number. 3. **Retire a code after a small number of failures against it**, and make every code strictly one-time — consumed on approval, never re-presentable. 4. **Do not leak which codes exist.** A verification page that distinguishes "no such code" from "that code is not yours" hands the attacker an oracle that turns guessing into enumeration. ## What the device contributes Only two things, and both are about not making it worse: display the `user_code` clearly enough that the user types it right the first time, and stop displaying it the moment the flow ends. A code still readable on a wall panel after the flow completed is a code sitting in front of whoever walks past. ## The trade-off to state out loud An interviewer asking this wants to hear that you know a short code is a *deliberate* weakness accepted for usability, not an oversight — and that accepting it obliges the authorization server to spend its own budget on rate limiting. Lengthening the code is the tempting answer and it is the wrong one: it pushes the failure into the user, who mistypes it, gives up, and asks a colleague to read it out.

  • Why can the device_code be long when the user_code cannot?
    Because no human ever handles it. The `device_code` is issued to the client on a back-channel response and returned on a back-channel token request; it is never displayed, read aloud or retyped, so length and randomness cost nothing. The `user_code` pays for every additional character in transcription errors and abandoned flows, which is why the two are sized by completely different criteria.
  • What does shortening expires_in actually buy, and what does it cost?
    It shrinks the window in which any outstanding `user_code` can be guessed, and it shrinks the number of codes live at once, which is the real search space. The cost is user-visible: a surveyor who puts the tablet down, walks to better signal and comes back finds the code dead and has to restart the flow. The lifetime should track how long approval genuinely takes on that hardware.
  • Should the verification page tell a user that the code they entered does not exist?
    It should say as little as it can while remaining usable. Distinguishing "unknown code" from "code belongs to another session" or "code already used" gives an attacker an oracle that converts blind guessing into enumeration. A single indistinguishable failure message, combined with a counted and capped attempt budget, keeps the page honest without teaching anyone the shape of the code space.

saying these in an interview costs you the question

  • Says a short user_code is simply a design oversight
  • Thinks the device can detect or block guessing attempts
  • Proposes a longer code as the whole fix
  • Assumes an attacker must be near the device to guess
  • Treats 34.5 bits as adequate because the code expires