OAuth 2.0 – Device flow explained for Engineers, especially for Backend Engineers

Published: (May 11, 2026 at 12:11 PM EDT)
4 min read

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

  1. The client (e.g., a CLI) requests a device code and a user code from the authorization server.
  2. The server returns the codes together with a verification URI and polling parameters.
  3. The client displays the user code and verification URI to the user.
  4. The user opens the URI on a separate device, enters the user code, authenticates, and approves the request.
  5. 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:

FieldMeaning
device_codeOpaque string kept private by the client.
user_codeShort, human‑readable code shown to the user.
verification_uriURL where the user enters the user_code.
expires_inLifetime of the device code (usually 15–30 min).
intervalMinimum 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

ResponseMeaningAction
authorization_pendingUser has not approved yet.Continue polling.
slow_downPolling too fast.Increase interval by ≥ 5 s.
expired_tokenDevice code expired.Stop polling, restart flow.
access_deniedUser 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

  1. Treat user_code as 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., no 0, O, I, 1). GitHub uses XXXX XXXX.

  2. Rate‑limit the verification endpoint
    The page where users submit user_code is 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.

  3. 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.

  4. Invalidate the user_code immediately after use
    Mark the code as consumed atomically. A “check‑then‑mark” race condition can allow reuse and cause security bugs.

  5. 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.

0 views
Back to Blog

Related posts

Read more »