Device Authorization Grant

This section documents OAuth 2.0 (RFC 6749) as amended by the OAuth 2.0 Security Best Current Practice (RFC 9700 / BCP 240), the OAuth 1.0 Protocol (RFC 5849) for historical context, and OpenID Connect Core 1.0, as published at the IETF Datatracker and the OpenID Foundation specifications — and, on the Spring pages, Spring Boot 4.1.x and Spring Security 7.1.x as published at the Spring Security reference documentation — which are the references these pages are written and verified against.

OAuth 2.1 is still an Internet-Draft (draft-ietf-oauth-v2-1-16, 3 September 2026) and is flagged as such everywhere it appears on these pages. It is a working-group consolidation in progress, not a published standard; nothing here should be read as saying otherwise.

This content was generated with the assistance of AI and should be verified against those official specifications before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

The device authorization grant (RFC 8628) exists for clients that can talk to the network but cannot reasonably render a browser or accept an inbound redirect at all: smart TVs, game consoles, streaming devices, printers, and headless CLIs running over SSH. Instead of redirecting the resource owner’s own user agent, the device displays a short code and asks the user to enter it on a second, capable device — a phone or laptop with a real browser. This page covers the full wire-level polling loop, the QR-code shortcut, and the cross-device phishing risk that shortcut introduces, together with RFC 10027’s mitigations.

The flow in one picture

sequenceDiagram participant D as Device (TV, console, CLI) participant AS as Authorization server participant U as User's phone or laptop browser D->>AS: POST /device_authorization\nclient_id, scope AS->>D: device_code, user_code,\nverification_uri, verification_uri_complete,\nexpires_in, interval D->>D: show user_code and verification_uri\n(or a QR code of verification_uri_complete) D->>AS: POST /token (poll every interval seconds)\ngrant_type=device_code, device_code, client_id AS->>D: 400 authorization_pending (not yet) U->>AS: open verification_uri, enter user_code\n(or scan QR code -> verification_uri_complete) AS->>U: authenticate + show device context + consent U->>AS: approve D->>AS: POST /token (next poll) AS->>D: 200 OK\naccess_token, token_type, expires_in, refresh_token

Step 1 — the device requests a code

The device starts the flow by asking the authorization server for a pair of codes: one for itself, one for the human.

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

client_id=1b4f3c00-3baa
&scope=openid%20profile%20offline_access
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://as.example.com/device",
  "verification_uri_complete": "https://as.example.com/device?user_code=WDJB-MJHT",
  "expires_in": 1800,
  "interval": 5
}
Field Meaning

device_code

A long, high-entropy value the device itself uses to poll the token endpoint. Never shown to the user.

user_code

A short, human-typeable value (RFC 8628 recommends a restricted character set that avoids visually ambiguous characters) the user enters on the verification page. Correlates the user’s browser session to this specific device’s pending authorization.

verification_uri

The URL the device tells the user to visit and type user_code into manually.

verification_uri_complete

The same URL with user_code already embedded as a query parameter — lets the device render a single scannable QR code or a clickable link that skips manual entry. Optional; see The QR-code shortcut for why it needs care.

expires_in

Seconds until device_code and user_code both expire; the device must stop polling and restart the whole flow after this.

interval

Minimum seconds the device must wait between polling requests; see [slow_down].

Step 2 — the device shows the code and starts polling

The device renders user_code and verification_uri prominently (and, optionally, a QR code encoding verification_uri_complete), then immediately starts polling the token endpoint with the device authorization grant, no earlier than every interval seconds:

POST /token HTTP/1.1
Host: as.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=1b4f3c00-3baa

The polling loop’s error responses

Every poll before the user finishes returns a 400 with one of these error values — each one tells the device exactly what to do next:

error HTTP status What the device does

authorization_pending

400

The user has not completed the verification step yet. Wait interval seconds and poll again — this is the expected, normal response for most of the polling loop’s lifetime.

slow_down

400

The device is polling too fast. Increase the polling interval by at least 5 seconds and continue — see [slow_down] below.

access_denied

400

The user explicitly declined the authorization request. Stop polling immediately and show a "declined" state; do not retry automatically.

expired_token

400

expires_in elapsed with no completed authorization. Stop polling and restart the whole flow from Step 1 with a fresh device_authorization request.

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

{
  "error": "authorization_pending"
}

slow_down exists so a device with a buggy or overly eager polling loop does not get treated as abusive traffic and rate-limited or blocked outright. Once received, the device must add at least 5 seconds to its current interval for all subsequent polls in this authorization attempt — it is a permanent adjustment for the rest of this flow, not a one-time back-off.

Step 3 — the user completes verification on a second device

Independently of the device’s polling loop, the user opens verification_uri (or scans the QR code, landing directly on verification_uri_complete) in a normal browser on their phone or laptop:

GET /device?user_code=WDJB-MJHT HTTP/1.1
Host: as.example.com

The authorization server authenticates the user through whichever authentication method applies, then shows a consent screen naming the device and the scope being requested — this device context is exactly what RFC 10027 requires to be explicit, for the reasons covered next. Once the user approves, the authorization server marks the underlying device_code as authorized; the device’s next poll succeeds:

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

{
  "access_token": "2YotnFZFEjr1zCsicMWpAA",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
  "scope": "openid profile offline_access"
}

The QR-code shortcut

Typing an eight-character code on a TV remote is tedious enough that most real deployments render verification_uri_complete as a QR code next to the on-screen user_code, letting the user’s phone camera jump straight to the pre-filled verification page. This is a genuine usability win, but it changes the threat model: a manually typed user_code requires the user to have read it off the actual device, while a QR code can be photographed, printed, or displayed anywhere and scanned by someone who never saw the real device at all — which is exactly the opening the next section covers.

Cross-device phishing and RFC 10027’s mitigations

Because the device showing the code and the browser completing verification are, by design, two different devices, an attacker can insert themselves in the middle: start a device-flow authorization on their own attacker-controlled "device" (which can be nothing more than a script), then trick a victim into completing that authorization on the victim’s own trusted browser — via a phishing e-mail, a fake tech-support call, or a QR code substituted at a public kiosk or printed sticker. The victim sees a real login page on the real authorization server, authenticates normally, and approves what looks like a legitimate request — but the resulting tokens go to the attacker’s device, not the victim’s.

RFC 10027 — the Best Current Practice for Security of Cross-Device Flows — addresses exactly this scenario with mitigations this page’s flow above already reflects:

  • Short-lived codes. A shorter expires_in (RFC 10027 recommends minutes, not the 30 minutes shown in this page’s example, for higher-risk deployments) shrinks the window an attacker has to lure a victim into completing someone else’s authorization.

  • Explicit device context on the consent screen. The verification page must tell the user identifiable information about which device is requesting authorization (device type, approximate location, or a descriptive name set at registration) rather than a bare "approve this login?" prompt — so a user asked to approve a "living-room TV" login while sitting at their desk has a concrete signal something is wrong.

  • Proximity checks. Where feasible, the authorization server correlates signals between the device requesting the code and the browser completing verification — for example, requiring them to be on the same local network, or comparing coarse IP geolocation — and either warns the user or refuses verification when they diverge sharply. This is the strongest mitigation but also the hardest to apply broadly, since it is incompatible with the flow’s core purpose of working across two genuinely separate devices and networks (a phone on mobile data verifying a TV on home Wi-Fi is a normal, legitimate case).

  • Rate limiting and anomaly detection on device_authorization requests themselves, since a phishing campaign at scale needs to mint many device codes in a short window.

None of these mitigations make the device authorization grant equivalent in security to authorization code
PKCE with a self-verified redirect — it inherently trusts that the human completing verification is looking at the device they think they are authorizing. Reserve this grant for the client shapes that genuinely cannot do better (no browser, no keyboard, no camera-scannable redirect), rather than as a convenience shortcut on a device that could instead run authorization code + PKCE directly.

Implementation notes for the device

A few practical points that do not show up in the wire-level exchange but matter to anyone building the device side of this flow:

  • Poll on a background timer, not a tight loop. The device must respect interval (and any later slow_down adjustment) as a minimum, not a target — polling early risks the authorization server treating the device as abusive and terminating the flow outright rather than simply returning slow_down.

  • Show a countdown or a "this code expires soon" cue as expires_in runs low, so the user is not left staring at a code that silently stopped working.

  • Restart cleanly on expired_token. Request a brand-new device_authorization pair rather than retrying the same device_code — it is dead the moment it expires, by design, to bound how long a leaked or shoulder-surfed user_code remains usable.

  • Treat access_denied as final for this attempt. Do not silently loop back into polling; show the user a clear "sign-in was declined" state and let them explicitly choose to try again, which starts Step 1 over.

  • Store the token the same way any other client would — see Access and Refresh Tokens for token storage and refresh-token rotation once the device has completed the flow; nothing about how the tokens are used afterwards differs from any other grant.

Device flow vs. authorization code + PKCE

Worth stating plainly, since the two are sometimes confused as interchangeable "browser-based" options:

Authorization code + PKCE Device authorization grant

Who completes login

The same physical device running the client, in its own browser

A second device, on a separate browser session

Redirect required

Yes — the client must receive an inbound redirect

No — the device only polls outbound; no inbound connectivity needed at all

Typical client

Server-side web app, SPA, native/mobile app, interactive CLI with a local browser

TV, console, kiosk, printer, headless CLI (SSH session, container shell)

Phishing surface

Standard redirect-URI and mix-up risks, covered on Security Best Practices

Cross-device phishing via a substituted code or QR, covered above under RFC 10027

Correlation to the requesting device

Implicit — the same browser that started the flow receives the redirect

Explicit — the authorization server must show device context on the consent screen precisely because the correlation is not automatic

Prefer authorization code + PKCE whenever the client can render a browser and receive a redirect at all; the device authorization grant is the fallback for the narrower set of clients that genuinely cannot, not a general-purpose alternative chosen for convenience.