Device Authorization Flow
The Device Authorization Flow (RFC 8628) enables OAuth 2.0 authorization for devices that either lack a browser or are input-constrained. This flow is particularly useful for smart TVs, gaming consoles, IoT devices, and CLI applications.
Overview
The Device Authorization Flow allows devices to obtain user authorization without requiring the user to enter credentials directly on the device. Instead, the user completes the authorization on a separate device with better input capabilities.
Flow Diagram
Device Authorization Server User's Browser
| | |
|---(A) Device Request-------->| |
| | |
|<--(B) Device Response--------| |
| | |
| |<---(C) User navigates to-----|
| | verification URI |
| | |
| |<---(D) User authorizes-------|
| | |
|---(E) Token Request--------->| |
| | |
|<--(F) Token Response---------| |
Step-by-Step Process
Step 1: Device Authorization Request
The device initiates the flow by requesting a device code from the authorization server.
Endpoint:
POST /oauth/device/code
Request:
curl -X POST "https://auth3.upbond.io/oauth/device/code" \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_CLIENT_ID",
"scope": "openid profile email"
}'
Request Parameters:
client_id
string
Yes
The client ID of your application
scope
string
No
Space-separated list of scopes
Step 2: Device Authorization Response
The authorization server responds with a device code and user code.
Response:
{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"user_code": "WDJB-MJHT",
"verification_uri": "https://auth3.upbond.io/activate",
"verification_uri_complete": "https://auth3.upbond.io/activate?user_code=WDJB-MJHT",
"expires_in": 1800,
"interval": 5
}
Response Parameters:
device_code
string
Device verification code (opaque to device)
user_code
string
User verification code (human-readable)
verification_uri
string
URI for user to visit for authorization
verification_uri_complete
string
Complete URI with user code included
expires_in
integer
Lifetime in seconds of device_code and user_code
interval
integer
Minimum interval in seconds between polling requests
Step 3: User Authorization
The device instructs the user to visit the verification URI and enter the user code.
Display to User:
Please visit: https://auth3.upbond.io/activate
Enter code: WDJB-MJHT
Or provide the QR code for the complete verification URI.
Step 4: Device Polls for Authorization
The device polls the token endpoint at regular intervals to check if the user has authorized the request.
Endpoint:
POST /oauth/token
Request:
curl -X POST "https://auth3.upbond.io/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"client_id": "YOUR_CLIENT_ID"
}'
Request Parameters:
grant_type
string
Yes
Must be "urn:ietf:params:oauth:grant-type:device_code"
device_code
string
Yes
Device code from step 2
client_id
string
Yes
The client ID of your application
Step 5: Token Response
Once the user authorizes the request, the server responds with tokens.
Success Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "GEbRxBN...edjnXbL",
"scope": "openid profile email",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Error Handling
Polling Errors
While polling, the device may receive the following errors:
Authorization Pending:
{
"error": "authorization_pending",
"error_description": "The authorization request is still pending"
}
Slow Down:
{
"error": "slow_down",
"error_description": "The device is polling too frequently"
}
Access Denied:
{
"error": "access_denied",
"error_description": "The user denied the authorization request"
}
Expired Token:
{
"error": "expired_token",
"error_description": "The device code has expired"
}
Error Codes
authorization_pending
User hasn't completed authorization
Continue polling
slow_down
Polling too frequently
Increase polling interval
access_denied
User denied authorization
Stop polling, restart flow
expired_token
Device code expired
Restart flow
invalid_client
Invalid client ID
Check client configuration
invalid_request
Invalid request parameters
Check request format
Implementation Examples
JavaScript/Node.js
const axios = require('axios');
class DeviceAuthFlow {
constructor(clientId, baseUrl = 'https://auth3.upbond.io') {
this.clientId = clientId;
this.baseUrl = baseUrl;
}
async initiateFlow(scope = 'openid profile email') {
try {
const response = await axios.post(`${this.baseUrl}/oauth/device/code`, {
client_id: this.clientId,
scope: scope
});
return response.data;
} catch (error) {
throw new Error(`Failed to initiate device flow: ${error.response.data.error_description}`);
}
}
async pollForToken(deviceCode, interval = 5) {
return new Promise((resolve, reject) => {
const poll = async () => {
try {
const response = await axios.post(`${this.baseUrl}/oauth/token`, {
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
device_code: deviceCode,
client_id: this.clientId
});
resolve(response.data);
} catch (error) {
const errorCode = error.response?.data?.error;
switch (errorCode) {
case 'authorization_pending':
// Continue polling
setTimeout(poll, interval * 1000);
break;
case 'slow_down':
// Increase interval and continue
setTimeout(poll, (interval + 5) * 1000);
break;
case 'access_denied':
case 'expired_token':
case 'invalid_client':
case 'invalid_request':
reject(new Error(error.response.data.error_description));
break;
default:
reject(error);
}
}
};
poll();
});
}
async authenticate(scope = 'openid profile email') {
try {
// Step 1: Get device code
const deviceAuth = await this.initiateFlow(scope);
// Step 2: Display instructions to user
console.log(`Please visit: ${deviceAuth.verification_uri}`);
console.log(`Enter code: ${deviceAuth.user_code}`);
console.log(`Or visit: ${deviceAuth.verification_uri_complete}`);
// Step 3: Poll for tokens
const tokens = await this.pollForToken(deviceAuth.device_code, deviceAuth.interval);
return tokens;
} catch (error) {
throw error;
}
}
}
// Usage
const auth = new DeviceAuthFlow('YOUR_CLIENT_ID');
auth.authenticate()
.then(tokens => {
console.log('Authentication successful:', tokens);
})
.catch(error => {
console.error('Authentication failed:', error.message);
});
Python
import requests
import time
class DeviceAuthFlow:
def __init__(self, client_id, base_url='https://auth3.upbond.io'):
self.client_id = client_id
self.base_url = base_url
def initiate_flow(self, scope='openid profile email'):
try:
response = requests.post(f'{self.base_url}/oauth/device/code', json={
'client_id': self.client_id,
'scope': scope
})
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f'Failed to initiate device flow: {e}')
def poll_for_token(self, device_code, interval=5):
while True:
try:
response = requests.post(f'{self.base_url}/oauth/token', json={
'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
'device_code': device_code,
'client_id': self.client_id
})
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
error_data = e.response.json() if e.response else {}
error_code = error_data.get('error')
if error_code == 'authorization_pending':
time.sleep(interval)
continue
elif error_code == 'slow_down':
time.sleep(interval + 5)
continue
elif error_code in ['access_denied', 'expired_token', 'invalid_client', 'invalid_request']:
raise Exception(error_data.get('error_description', str(e)))
else:
raise e
def authenticate(self, scope='openid profile email'):
try:
# Step 1: Get device code
device_auth = self.initiate_flow(scope)
# Step 2: Display instructions to user
print(f"Please visit: {device_auth['verification_uri']}")
print(f"Enter code: {device_auth['user_code']}")
print(f"Or visit: {device_auth['verification_uri_complete']}")
# Step 3: Poll for tokens
tokens = self.poll_for_token(device_auth['device_code'], device_auth['interval'])
return tokens
except Exception as e:
raise e
# Usage
auth = DeviceAuthFlow('YOUR_CLIENT_ID')
try:
tokens = auth.authenticate()
print('Authentication successful:', tokens)
except Exception as e:
print('Authentication failed:', str(e))
Best Practices
Polling Interval: Respect the
interval
parameter to avoid rate limitingError Handling: Implement proper error handling for all error codes
User Experience: Provide clear instructions and visual feedback
QR Codes: Consider generating QR codes for the verification URI
Timeout Handling: Handle expired tokens gracefully
Security: Validate all tokens before use
Use Cases
Smart TVs: Netflix, YouTube, Spotify apps
Gaming Consoles: PlayStation, Xbox authentication
IoT Devices: Smart home devices, security cameras
CLI Applications: Command-line tools requiring user authentication
Media Players: Roku, Apple TV, Chromecast apps
Security Considerations
Device Code Security: Device codes should be unpredictable and single-use
User Code Format: User codes should be easy to type and read
Expiration: Device codes should have reasonable expiration times
Rate Limiting: Implement rate limiting on polling endpoints
HTTPS: Always use HTTPS for all communications
Support
For Device Authorization Flow support:
Documentation: OAuth 2.0 Device Authorization Grant
Technical Support: support@upbond.io
Integration Help: developers@upbond.io
Last updated
Was this helpful?