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 devicecode and usercode; 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
authorizationpending,slowdown,expiredtoken, andaccessdeniedto 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 devicecode, human-entered usercode, verificationuri, 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 authorizationpending, 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 devicecode, easy-to-enter usercode, 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 devicecode, usercode, verificationuri, expiresin, and interval. verificationuricomplete 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:
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 authorizationpending; after slowdown, 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 expiredtoken, accessdenied, 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 devicecode, 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 authorizationpending, 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_downand 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 verificationuricomplete?
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 slowdown, terminal latency, and client-aggregated failures. Inject repeated polls in controlled tests and verify that slowdown increases the client interval by at least 5 seconds without a synchronized storm. Isolate abusive clients instead of relaxing the global interval.