SDKsTypeScript SDKOAuth 2.1
Device Authorization Flow
Authenticate devices without browsers (CLI tools, IoT, smart TVs, AI agents) using the TypeScript SDK.
Device Authorization Flow
The device authorization flow (RFC 8628) enables authentication on devices with limited input capabilities -- CLI tools, smart TVs, IoT devices, and AI agents. The user authorizes the device by entering a code on a separate device with a browser.
requestDeviceCode()
Request a device code and user code. The user visits the verification URL on a browser and enters the user code.
client.oauth.requestDeviceCode(params: DeviceCodeParams): Promise<DeviceCodeResponse>Parameters
| Name | Type | Required | Description |
|---|---|---|---|
scopes | string[] | yes | OAuth scopes to request |
Returns
interface DeviceCodeResponse {
deviceCode: string; // Device code for polling (do NOT show to user)
userCode: string; // User code to display (e.g., "ABCD-1234")
verificationUri: string; // URL for the user to visit
verificationUriComplete: string; // URL with code pre-filled
expiresIn: number; // Seconds until the codes expire (typically 900)
interval: number; // Minimum polling interval in seconds (typically 5)
}Basic usage
import { AvnologyId } from "@avnology/sdk-typescript";
const client = new AvnologyId({
baseUrl: "https://api.id.avnology.com",
clientId: "app_cli_tool",
});
// Step 1: Request device code
const device = await client.oauth.requestDeviceCode({
scopes: ["openid", "profile", "email"],
});
// Step 2: Display instructions to the user
console.log("To sign in, visit:", device.verificationUri);
console.log("Enter code:", device.userCode);
console.log("");
console.log("Or visit this link directly:");
console.log(device.verificationUriComplete);pollDeviceToken()
Poll the token endpoint until the user authorizes the device or the code expires.
client.oauth.pollDeviceToken(params: PollDeviceTokenParams): Promise<TokenSet>Parameters
| Name | Type | Required | Description |
|---|---|---|---|
deviceCode | string | yes | Device code from requestDeviceCode() |
interval | number | no | Polling interval in seconds (default: from device code response) |
timeout | number | no | Maximum time to poll in ms (default: from expiresIn) |
onPending | () => void | no | Callback on each pending poll (for spinner/progress) |
Returns
Promise<TokenSet> -- Tokens after successful authorization.
Basic usage
// Step 3: Poll until the user authorizes
const tokens = await client.oauth.pollDeviceToken({
deviceCode: device.deviceCode,
interval: device.interval,
});
console.log("Authenticated!");
console.log("Access token:", tokens.accessToken);Complete CLI flow
import { AvnologyId, DeviceFlowExpiredError } from "@avnology/sdk-typescript";
async function cliLogin() {
const client = new AvnologyId({
baseUrl: "https://api.id.avnology.com",
clientId: "app_cli_tool",
});
// Request device code
const device = await client.oauth.requestDeviceCode({
scopes: ["openid", "profile", "email", "offline_access"],
});
// Show the user what to do
console.log("\n Avnology ID Login\n");
console.log(` 1. Open your browser to: ${device.verificationUri}`);
console.log(` 2. Enter the code: ${device.userCode}\n`);
console.log(` Or visit: ${device.verificationUriComplete}\n`);
console.log(" Waiting for authorization...\n");
try {
// Poll with progress indicator
let dots = 0;
const tokens = await client.oauth.pollDeviceToken({
deviceCode: device.deviceCode,
interval: device.interval,
onPending: () => {
dots = (dots + 1) % 4;
process.stdout.write(`\r Waiting${".".repeat(dots)}${" ".repeat(3 - dots)}`);
},
});
console.log("\r Authenticated! ");
// Store tokens locally
const fs = await import("fs");
const os = await import("os");
const path = await import("path");
const tokenPath = path.join(os.homedir(), ".avnology", "tokens.json");
fs.mkdirSync(path.dirname(tokenPath), { recursive: true });
fs.writeFileSync(tokenPath, JSON.stringify(tokens, null, 2));
console.log(` Tokens saved to ${tokenPath}`);
console.log(` Logged in as: ${client.oauth.decodeIdToken(tokens.idToken).email}`);
} catch (error) {
if (error instanceof DeviceFlowExpiredError) {
console.error("\n Device code expired. Please try again.");
process.exit(1);
}
throw error;
}
}AI agent authentication
import { AvnologyId } from "@avnology/sdk-typescript";
async function authenticateAgent() {
const client = new AvnologyId({
baseUrl: "https://api.id.avnology.com",
clientId: "app_ai_agent",
});
const device = await client.oauth.requestDeviceCode({
scopes: ["openid", "profile", "offline_access"],
});
// Send verification URL to user via chat, email, or notification
sendToUser({
message: `Please authorize this AI agent by visiting ${device.verificationUriComplete}`,
userCode: device.userCode,
});
// Wait for user authorization
const tokens = await client.oauth.pollDeviceToken({
deviceCode: device.deviceCode,
});
// Agent is now authenticated and can act on behalf of the user
client.setTokens(tokens);
return client;
}Common errors
| Error class | HTTP status | When |
|---|---|---|
DeviceFlowExpiredError | 400 | Device code expired before user authorized |
DeviceFlowDeniedError | 400 | User explicitly denied authorization |
DeviceFlowSlowDownError | 400 | Polling too fast (SDK handles this automatically) |
RateLimitError | 429 | Too many polling requests |
See also
- Client credentials -- M2M auth (no user interaction)
- Token exchange -- Delegation and impersonation
- Tokens -- Token refresh and revocation