Representative interview topic

Backend interview: Design an OAuth 2.0 Device Authorization Grant

BackendHard
Offer.cc Editorial TeamPublished Updated

Question

Design an OAuth 2.0 Device Authorization Grant for a TV with no suitable browser or text input: the device displays a user code, the user authorizes on a phone, and the device polls for tokens. Define the APIs, state machine, polling policy, expiry, and security boundaries.

Prompt and scope

This is an OAuth protocol and security-design problem. RFC 8628 targets smart TVs, media consoles, printers, and similar devices that cannot conveniently enter text or run a browser. The device requests a device_code and user_code; the user signs in and authorizes on a separate browser; the original device polls the token endpoint. Such devices are usually public clients and cannot hide a long-lived client secret in firmware. The exercise does not replace the authorization-code flow for native apps that can use the system browser.

What the interviewer is testing

  • Distinguishing the lifetimes of device codes, user codes, verification URIs, access tokens, and refresh tokens.
  • Mapping authorization_pending, slow_down, expired_token, and access_denied to states.
  • Designing bounded polling, backoff, idempotency, and concurrency protection.
  • Recognizing user-code phishing, device impersonation, brute-force guessing, and public-client risks.

Clarifications to ask first

Confirm whether the device can display a URI and code, make HTTPS requests, and keep a reliable clock, and whether the authorization server supports OIDC sign-in. Clarify requested resources, device binding, who sets code lifetime and polling interval, and whether cancellation is required. If the device already has a system browser, prefer the RFC 8252 authorization-code flow instead of forcing Device Flow.

A 30-second answer

Implement two endpoints. The device-authorization endpoint returns a short-lived device_code, human-entered user_code, verification_uri, expiry, and minimum interval. The token endpoint accepts only the device code and returns tokens or standard errors. Store a hash of the device code, status, client, and expiry. Poll at the server-provided interval: continue on authorization_pending, increase the interval on slow_down, and stop on success, expiry, denial, or permanent invalidity. Show device information on the user page to reduce phishing.

Step-by-step solution

1. Model participants and one authorization session

Participants are the constrained device, authorization server, user browser, and resource server. The device-authorization endpoint creates a session with high-entropy device_code, easy-to-enter user_code, client, requested scopes, creation time, expiry, and polling state. Store only a one-way digest of the device code so a database read does not directly mint a token; index the user code separately and cap attempts.

2. Design the device-authorization response

Return at least device_code, user_code, verification_uri, expires_in, and interval. verification_uri_complete can offer a convenient link, but display the same short code on the device and user page and ask the user to confirm the device in front of them. Protect the code from logs, analytics, and screenshots. The server must not put an access token in this response.

3. Handle user interaction and binding

After opening the verification URI, the user signs in and sees the client name, requested scopes, and device information. The consent page should make the user confirm that the device is theirs, reducing authorization of an attacker’s device. Approval changes the session to AUTHORIZED; it does not send a token through the browser to the device. A denial is a terminal DENIED state that the next poll reports explicitly.

4. Define the token-endpoint state machine

The token endpoint drives device state with standard errors:

text
poll(device_code):
  session = lookup(hash(device_code))
  if session is missing: return invalid_grant
  if now >= session.expiresAt: return expired_token
  if session.status == DENIED: return access_denied
  if session.status != AUTHORIZED: return authorization_pending
  atomically mark CONSUMED
  return issueAccessAndRefreshTokens(session)

Successful redemption of one device code must be atomic so two device threads cannot receive two token sets. If a code is single-use, a repeated request returns invalid_grant or the protocol’s consumed error; a failed retry must not create a new authorization session.

5. Apply polling backoff and server protection

The device must not poll sooner than the response’s interval. Keep that interval after authorization_pending; after slow_down, increase it by at least 5 seconds. Add a total deadline, network timeouts, and jitter on the device. Rate-limit by device code, client, and IP on the server. Token responses should forbid caching so intermediaries do not replay state; during load, return a retryable error rather than creating an expensive background task per poll.

6. Handle expiry, cancellation, and recovery

Treat expired_token, access_denied, and permanent invalid_grant as terminal. Clear the local device code and stop polling. A device cancel action marks the session CANCELLED; later requests cannot revive it. On a network outage, retain the deadline and resume the same code instead of creating several sessions that can authorize the wrong device. After a reboot, restart and tell the user if the device cannot safely persist session state.

7. Secure public clients and resist phishing

Assume the device cannot protect a client secret. Minimize scopes, and rotate and revoke refresh tokens. Make the user code short enough to enter but random enough to resist online guessing; rate-limit failed attempts by code and IP. Show the device name, model, or one-time code and ask the user to compare it with the screen. Logs contain only a session ID, result, and hashed identifiers, never the raw device code or tokens.

8. Instrument and test the flow

Measure device-authorization creation, completion, time to completion, polls per session, slow_down rate, expiry and denial, and token-redemption races. Test duplicate redemption, timeout, clock skew, network retries, guessing, concurrent polls, user denial, server restart, and throttling. An end-to-end test must prove that the approved device and final token session match; two endpoints returning 200 independently is insufficient.

Model answer

I would create a one-time RFC 8628 session. The device endpoint returns a high-entropy device_code, short user code, verification URI, expiry, and minimum polling interval; the server stores a hashed code and explicit status. The user signs in on a phone, reviews client, scopes, and device information, and approves. The token endpoint accepts only the device code: continue on authorization_pending, add at least 5 seconds on slow_down, and terminate on expiry, denial, or permanent invalidity. Consume the code atomically so concurrent polls cannot issue two token sets. Treat the device as a public client, minimize scopes, rotate refresh tokens, limit guessing, and ask the user to compare the code on both screens. Metrics and tests cover flow binding, backoff, recovery, and phishing boundaries.

Common mistakes

  • Treating the device code as the human-entered code and exposing a high-value credential in UI or logs.
  • Ignoring slow_down and polling the token endpoint at a fixed high rate.
  • Retrying every error, including expiry and denial, forever.
  • Pretending a static device secret makes a public client confidential.
  • Sending an access token directly through a browser URL or page script after approval.
  • Failing to atomically consume the device code and issuing multiple refresh tokens.
  • Testing polling endpoints separately without proving the approved device matches the token session.

Follow-up questions

How do you choose user-code length and lifetime?

Balance entry convenience, online-guessing cost, and completion time. Avoid ambiguous characters, cap failed attempts and aggregate rate, and make the lifetime long enough for the user to retrieve a phone, sign in, and confirm the device without creating an unlimited phishing window. Calibrate numbers with the threat model and observed completion time.

How do you prevent a malicious client from filling all sessions?

Rate-limit creation by client registration, IP, device fingerprint, and account; cap unfinished sessions per principal and clean expired sessions asynchronously. Validate the client and scopes before creating a session. Do not move garbage device-code cleanup pressure to the token endpoint.

How does an offline device recover after approval?

Keep the session until expiry so the device can poll the same code after reconnecting. Return either the token set or a terminal state. Make redemption handling idempotent so a lost response does not cause a second consumption. After expiry, restart with a new code and inform the user.

What are the risks of verification_uri_complete?

Putting the user code in a link reduces typing but increases link leakage and remote-phishing risk. Still display the short code and ask the user to compare it with the device screen. Do not expose the complete link to analytics, Referer headers, history, or third-party redirects. Offer it only when device capability and the threat model allow it.

Why not transfer the access token from the phone to the device?

Cross-device token transfer expands exposure through the browser, QR code, clipboard, and intermediate pages, and it is hard to prove the recipient is the right device. Device Flow lets the device redeem its own code over TLS; the user page changes only server state. Add a separately authenticated near-field channel if the product requires one.

How do you verify that polling backs off correctly?

Record session-level poll counts, interval distributions, actual intervals after slow_down, terminal latency, and client-aggregated failures. Inject repeated polls in controlled tests and verify that slow_down increases the client interval by at least 5 seconds without a synchronized storm. Isolate abusive clients instead of relaxing the global interval.

Public sources

Related questions