Device Authorization Flow

RFC 8628 device authorization for CLIs and headless devices

Updated Dec 29, 2025 Edit this page

Device Authorization Flow

RFC 8628 device authorization grant for CLI tools and headless devices.

Overview

The device flow allows authentication on devices that lack a browser or have limited input capabilities (CLIs, smart TVs, IoT devices).

sequenceDiagram
    participant CLI
    participant AuthOS
    participant Browser
    participant User

    CLI->>AuthOS: POST /auth/device/code
    AuthOS-->>CLI: {device_code, user_code, verification_uri}
    CLI->>User: Display: "Go to sso.example.com/device"
    CLI->>User: Display: "Enter code: ABCD-EFGH"
    User->>Browser: Navigate to verification_uri
    User->>Browser: Enter user_code
    Browser->>AuthOS: POST /auth/device/verify
    AuthOS-->>Browser: Show login options
    User->>Browser: Complete OAuth login
    
    loop Poll every 5 seconds
        CLI->>AuthOS: POST /auth/token
        AuthOS-->>CLI: "Not authorized" or tokens
    end
    
    CLI->>User: Logged in!

Endpoints

Method Path Description
POST /auth/device/code Request device codes
POST /auth/device/verify Verify user code (web)
POST /auth/token Exchange device code for token

POST /auth/device/code

Request device authorization codes for a CLI or headless device.

Synopsis

Property Value
Authentication Public
Code Lifetime 15 minutes

Request Body

Field Type Required Description
client_id string Yes Service client ID
org string Yes Organization slug
service string Yes Service slug

Example Request

curl -X POST https://sso.example.com/auth/device/code \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "service_abc123xyz",
    "org": "acme-corp",
    "service": "cli-tool"
  }'

Response (200 OK)

{
  "device_code": "550e8400-e29b-41d4-a716-446655440000",
  "user_code": "ABCD-EFGH",
  "verification_uri": "https://sso.example.com/device",
  "expires_in": 900,
  "interval": 5
}

Response Fields

Field Type Description
device_code string Code for token exchange (keep secret)
user_code string Code for user to enter (display to user)
verification_uri string URL for user to visit
expires_in integer Seconds until codes expire
interval integer Minimum seconds between polling

Errors

Status Condition
400 Invalid client_id
400 Service not configured for device flow

POST /auth/device/verify

Verify user code and return authentication context. Called by web frontend.

Synopsis

Property Value
Authentication Public
Called By Web verification page

Request Body

Field Type Required Description
user_code string Yes User code from device (e.g., “ABCD-EFGH”)

Example Request

curl -X POST https://sso.example.com/auth/device/verify \
  -H "Content-Type: application/json" \
  -d '{
    "user_code": "ABCD-EFGH"
  }'

Response (200 OK)

{
  "org_slug": "acme-corp",
  "service_slug": "cli-tool",
  "available_providers": ["github", "google", "microsoft"]
}

Errors

Status Condition
400 Invalid or expired user code
400 Device already authorized

POST /auth/token

Exchange device code for access token. Poll this endpoint until authorization completes.

Synopsis

Property Value
Authentication Public
Poll Interval 5 seconds minimum

Request Body

Field Type Required Description
grant_type string Yes urn:ietf:params:oauth:grant-type:device_code
client_id string Yes Service client ID
device_code string Yes Device code from /auth/device/code

Example Request

curl -X POST https://sso.example.com/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
    "client_id": "service_abc123xyz",
    "device_code": "550e8400-e29b-41d4-a716-446655440000"
  }'

Response - Success (200 OK)

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 86400
}

Response - Authorization Pending (401 Unauthorized)

{
  "error": "Not authorized"
}

Continue polling when you receive this response.

Response - Expired (400 Bad Request)

{
  "error": "Device code expired"
}

Stop polling and start new device flow.

Errors

Status Error Action
401 Not authorized Continue polling
400 Device code expired Start new flow
400 Invalid grant type Fix request

CLI Implementation Example

async function deviceLogin() {
  // 1. Request device codes
  const codeResponse = await fetch('https://sso.example.com/auth/device/code', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      client_id: 'service_abc123xyz',
      org: 'acme-corp',
      service: 'cli-tool'
    })
  });
  
  const { device_code, user_code, verification_uri, interval } = await codeResponse.json();
  
  // 2. Display instructions to user
  console.log(`\nTo login, visit: ${verification_uri}`);
  console.log(`And enter code: ${user_code}\n`);
  
  // 3. Poll for authorization
  while (true) {
    await sleep(interval * 1000);
    
    const tokenResponse = await fetch('https://sso.example.com/auth/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
        client_id: 'service_abc123xyz',
        device_code
      })
    });
    
    const data = await tokenResponse.json();
    
    if (data.access_token) {
      console.log('Login successful!');
      return data.access_token;
    }
    
    if (data.error !== 'Not authorized') {
      throw new Error(data.error);
    }
  }
}

Security Considerations

  1. Device code is secret - Never display to user
  2. User code is public - Safe to display
  3. Polling interval - Respect 5-second minimum
  4. Code expiry - 15-minute window for user to authorize