Skip to content

Device Flow

Device Flow is an OAuth 2.0 extension designed for input-constrained devices (smart TVs, IoT devices, CLI tools) that lack a web browser or have limited input capabilities. Users authenticate on a separate device (smartphone/PC) using a simple verification code or QR code.

Overview

Why Use Device Flow?

Key Benefits

  1. Input-Constrained Devices

    • Perfect for devices without keyboards (Smart TVs, streaming boxes)
    • No need for embedded web browser on the device
    • Simple 8-character verification code (e.g., WDJB-MJHT)
    • QR code support for instant scanning
  2. Secure Authentication

    • User authenticates on their trusted device (smartphone/PC)
    • OAuth 2.0 security model with PKCE support
    • One-time use device codes (prevents replay attacks)
    • Automatic code expiration (10 minutes default)
  3. Excellent User Experience

    • No complex URL typing on TV remotes
    • Scan QR code or enter short code (8 characters)
    • Authenticate using familiar device (phone/PC)

Use Cases

  • Smart TV Apps (Netflix/YouTube-style login)
  • IoT Devices (smart home devices, security cameras)
  • CLI Tools (GitHub CLI, AWS CLI, developer tools)
  • Gaming Consoles (Xbox, PlayStation login flows)
  • Kiosk Terminals (public terminals with limited input)

How It Works

Flow Overview

  1. Device requests authorization: Device sends POST /device_authorization
  2. Server returns codes: Returns device_code, user_code, verification_uri
  3. Device displays code: Shows QR code and user code on screen
  4. User authenticates: User visits URL on phone/PC and enters code
  5. Device polls for tokens: Device polls /token endpoint
  6. Server returns tokens: After user approval, returns tokens

API Reference

1. Device Authorization Endpoint

POST /device_authorization

Request

POST /device_authorization HTTP/1.1
Content-Type: application/x-www-form-urlencoded
client_id=tv_app_123&scope=openid+profile+email

Response

{
"device_code": "4c9a8e6f-b2d1-4a7c-9e3f-1d2b4a7c9e3f",
"user_code": "WDJB-MJHT",
"verification_uri": "https://auth.example.com/device",
"verification_uri_complete": "https://auth.example.com/device?user_code=WDJB-MJHT",
"expires_in": 600,
"interval": 5
}
FieldTypeDescription
device_codestringUUID for polling (keep secret)
user_codestring8-char code for user entry
verification_uristringURL for manual code entry
verification_uri_completestringURL with code pre-filled
expires_innumberCode expiration in seconds
intervalnumberMinimum polling interval in seconds

2. Token Endpoint (Polling)

POST /token

POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=4c9a8e6f-b2d1-4a7c-9e3f-1d2b4a7c9e3f
&client_id=tv_app_123

Response States

Pending (keep polling):

{
"error": "authorization_pending",
"error_description": "User has not yet authorized the device"
}

Too Fast (slow down):

{
"error": "slow_down",
"error_description": "You are polling too frequently. Please slow down."
}

Success:

{
"access_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_...",
"id_token": "eyJhbGc...",
"scope": "openid profile email"
}

Error Codes:

  • authorization_pending - User hasn’t approved yet (keep polling)
  • slow_down - Polling too fast (increase interval by 5 seconds)
  • access_denied - User denied the request
  • expired_token - Device code expired (restart flow)

Usage Examples

Smart TV App Integration

// Step 1: Request device code
const response = await fetch('https://auth.example.com/device_authorization', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: 'smart_tv_app_123',
scope: 'openid profile email'
})
});
const data = await response.json();
// Display: QR Code for verification_uri_complete
// Display: "Enter code: WDJB-MJHT"
// Step 2: Poll for authorization
const pollInterval = data.interval * 1000;
let currentInterval = pollInterval;
const pollForAuthorization = async () => {
const tokenResponse = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
device_code: data.device_code,
client_id: 'smart_tv_app_123'
})
});
if (tokenResponse.ok) {
const tokens = await tokenResponse.json();
console.log('Logged in!', tokens);
return tokens;
}
const error = await tokenResponse.json();
if (error.error === 'authorization_pending') {
setTimeout(pollForAuthorization, currentInterval);
} else if (error.error === 'slow_down') {
currentInterval += 5000;
setTimeout(pollForAuthorization, currentInterval);
} else {
console.error('Login failed:', error.error_description);
}
};
setTimeout(pollForAuthorization, currentInterval);

CLI Tool Integration

Terminal window
$ awesome-cli login
Device Login
Visit: https://auth.example.com/device
And enter code: WDJB-MJHT
Or scan this QR code:
[QR Code]
Waiting for authorization...
Login successful! You're now authenticated.

Security Considerations

Device Code Entropy

  • UUID v4 provides 122 bits of entropy
  • Prevents guessing attacks

Code Expiration

  • Default: 600 seconds (10 minutes)
  • Automatic cleanup via Durable Object alarms

Rate Limiting

  • Minimum interval: 5 seconds (default)
  • slow_down error if polling too fast
  • Max poll count: 120 polls

One-Time Use

  • Device codes invalidated after first use
  • Replay attack detection

User Code Design

  • Charset: 23456789ABCDEFGHJKMNPQRSTUVWXYZ
  • Excludes confusing characters: 0, O, 1, I, L
  • Format: XXXX-XXXX (hyphen for readability)

Troubleshooting

”Invalid or expired verification code”

Cause: Code expired (10 minutes) or already used

Solution: Restart device flow to get a new code

”slow_down” error during polling

Cause: Client polling faster than allowed interval

Solution: Increase polling interval by 5 seconds

UI_BASE_URL not configured

Cause: Environment variable not set

Solution: Set UI_BASE_URL in wrangler.toml or .dev.vars

References