OAuth 2.0 – Device flow explained for Engineers, especially for Backend Engineers
Source: Stack Overflow Blog
First time I tried to log in to Netflix on a hotel TV I almost gave up. The remote had only four arrow keys and a number pad, and my password was 18 characters with symbols. After a few years the same TVs started showing a short code and a URL. I opened my phone, typed the URL, entered the code, and we were in—no remote‑control circus, no password on a TV.
That is the OAuth 2.0 device authorization grant, often called the device flow.
If you have ever run aws sso login, gh auth login, or signed into Spotify on an Xbox, you have already used it. When building a backend for a CLI, IoT device, smart‑TV app, or any input‑constrained client, you will eventually need to implement it.
Overview of the Device Flow
- The client (e.g., a CLI) requests a device code and a user code from the authorization server.
- The server returns the codes together with a verification URI and polling parameters.
- The client displays the user code and verification URI to the user.
- The user opens the URI on a separate device, enters the user code, authenticates, and approves the request.
- Meanwhile the client polls the token endpoint until the user completes the step or an error occurs.
If the client handles the five possible polling responses correctly, the flow is complete.
Step‑by‑step implementation
Step 1 – Request a device code
POST /oauth/device_authorization HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
client_id=mycli-prod&scope=read:repos%20write:repos
Typical response
{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"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
}
Important fields:
| Field | Meaning |
|---|---|
device_code | Opaque string kept private by the client. |
user_code | Short, human‑readable code shown to the user. |
verification_uri | URL where the user enters the user_code. |
expires_in | Lifetime of the device code (usually 15–30 min). |
interval | Minimum polling interval in seconds. |
Step 2 – Show instructions to the user
Open this URL in your browser:
https://example.com/device
Enter this code:
WDJB-MJHT
Waiting for confirmation...
The user opens the URL on a phone or laptop, enters the code, signs in (e.g., via SSO), and approves the request.
Step 3 – Poll the token endpoint
POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
&client_id=mycli-prod
Possible responses
| Response | Meaning | Action |
|---|---|---|
authorization_pending | User has not approved yet. | Continue polling. |
slow_down | Polling too fast. | Increase interval by ≥ 5 s. |
expired_token | Device code expired. | Stop polling, restart flow. |
access_denied | User denied the request. | Stop polling, inform user. |
| Success (200) | Token granted. | Use the returned tokens. |
Success payload example
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "v1.MRn7...",
"scope": "read:repos write:repos"
}
Common pitfalls and best‑practice tips
-
Treat
user_codeas public, not secret
It is entered by the user on a separate device, so it should be short but unguessable. A typical format is 8 – 9 alphanumeric characters without ambiguous symbols (e.g., no0,O,I,1). GitHub usesXXXX XXXX. -
Rate‑limit the verification endpoint
The page where users submituser_codeis a brute‑force target. Even with a reduced alphabet, thousands of pending codes may exist simultaneously. Apply per‑IP or per‑session limits and exponential back‑off after failures. -
Respect
slow_down
Some clients ignore this and keep polling at the original interval, which can trigger abuse detection. Always honor the server‑requested increase. -
Invalidate the
user_codeimmediately after use
Mark the code as consumed atomically. A “check‑then‑mark” race condition can allow reuse and cause security bugs. -
Don’t confuse device flow with PKCE
Device flow solves the problem of input‑constrained devices. PKCE is for public clients (mobile apps, SPAs) that cannot keep a secret. They can be combined (device flow + PKCE) when appropriate.
Closing thoughts
The device flow boils down to two endpoints and five response cases. Once you have a working implementation, you’ll wonder why anyone still asks users to paste personal access tokens into a CLI prompt. Implement it once, and your users will thank you.