Skip to content

Device Authorization Grant

Overview

The Device Authorization Grant (RFC 8628) enables authentication on devices with limited input capabilities — smart TVs, CLI tools, IoT devices, and gaming consoles. The user authenticates on a separate device (e.g., a smartphone) by entering a short code.

sequenceDiagram
    participant D as Device (TV/CLI)
    participant SDK as SDK
    participant AS as Auth Server
    participant B as User's Browser

    D->>SDK: Start flow
    SDK->>AS: Device authz request
    AS->>SDK: user_code + verification_uri
    SDK->>D: Show code
    B->>AS: Visit URL + enter code
    B->>AS: Authenticate
    SDK->>AS: Poll token
    AS->>SDK: Tokens
    SDK->>D: Authenticated

Starting the Device Flow

Use client.deviceFlow.start() to initiate the flow:

const state = await client.deviceFlow.start({
scope: 'openid profile',
});
// Display to the user
console.log(`Go to: ${state.verificationUri}`);
console.log(`Enter code: ${state.userCode}`);

DeviceFlowState

PropertyTypeDescription
deviceCodestringDevice verification code (used internally for polling)
userCodestringShort code displayed to the user
verificationUristringURL where the user enters the code
verificationUriCompletestring | undefinedURL with the code pre-filled (for QR codes)
expiresAtnumberWhen the device code expires (epoch seconds)
intervalnumberMinimum polling interval in seconds

Start Options

ParameterTypeDefaultDescription
scopestring'openid'Requested scopes
extraParamsRecord<string, string>Additional parameters

Polling for Completion

Automatic Polling

Use pollUntilComplete() to automatically poll until the user completes authentication:

const state = await client.deviceFlow.start({ scope: 'openid profile' });
// Display code to user...
// Poll until complete (handles slow_down and retries)
const tokens = await client.deviceFlow.pollUntilComplete(state);
console.log('Authenticated:', tokens.accessToken);

Cancellation with AbortSignal

const controller = new AbortController();
// Cancel after 5 minutes
setTimeout(() => controller.abort(), 5 * 60 * 1000);
try {
const tokens = await client.deviceFlow.pollUntilComplete(state, {
signal: controller.signal,
});
} catch (error) {
if (error.code === 'device_authorization_expired') {
console.log('Device code expired');
}
}

Manual Polling

For custom polling logic, use pollOnce():

const state = await client.deviceFlow.start();
// Custom polling loop
let result;
do {
result = await client.deviceFlow.pollOnce(state);
switch (result.status) {
case 'pending':
// User hasn't completed yet
await sleep(result.retryAfter * 1000);
break;
case 'slow_down':
// Server requests slower polling
await sleep(result.retryAfter * 1000);
break;
case 'completed':
console.log('Tokens:', result.tokens);
break;
case 'expired':
console.error('Device code expired');
break;
case 'access_denied':
console.error('User denied the request');
break;
}
} while (result.status === 'pending' || result.status === 'slow_down');

DeviceFlowPollResult

StatusPropertiesDescription
'pending'retryAfter: numberUser hasn’t authorized yet
'slow_down'retryAfter: numberPoll interval increased by server
'completed'tokens: TokenSetAuthentication successful
'expired'Device code has expired
'access_denied'User denied authorization

Checking Availability

Before using the device flow, verify that the authorization server supports it:

const isAvailable = await client.deviceFlow.isAvailable();
if (isAvailable) {
const state = await client.deviceFlow.start();
// ...
} else {
console.log('Device flow not supported by this server');
}

Complete Example: CLI Application

import { createAuthrimClient } from '@authrim/core';
async function cliLogin() {
const client = await createAuthrimClient({
issuer: 'https://auth.example.com',
clientId: 'cli-app',
crypto: nodeCryptoProvider,
storage: fileStorageProvider,
http: nodeHttpClient,
});
// Check availability
if (!(await client.deviceFlow.isAvailable())) {
console.error('Device flow not supported');
process.exit(1);
}
// Start device flow
const state = await client.deviceFlow.start({
scope: 'openid profile email',
});
// Display instructions
console.log('');
console.log(' To sign in, open this URL in your browser:');
console.log(` ${state.verificationUri}`);
console.log('');
console.log(` Enter code: ${state.userCode}`);
console.log('');
// If available, show QR code
if (state.verificationUriComplete) {
console.log(` Or scan: ${state.verificationUriComplete}`);
}
console.log(' Waiting for authentication...');
// Poll until complete
try {
const tokens = await client.deviceFlow.pollUntilComplete(state);
console.log(' Login successful!');
const user = await client.getUser();
console.log(` Welcome, ${user.name}!`);
} catch (error) {
if (error.code === 'device_authorization_expired') {
console.error(' Code expired. Please try again.');
} else if (error.code === 'device_access_denied') {
console.error(' Authorization denied.');
} else {
console.error(' Login failed:', error.message);
}
}
}

Error Handling

Error CodeDescriptionRecovery
device_authorization_errorFailed to start device flowRetry or check configuration
device_authorization_expiredDevice code expired before user authenticatedRestart the flow
device_access_deniedUser denied the authorization requestInform user, allow retry

Next Steps