Optional
Sign in devices without a keyboard through a second screen
For TVs, command-line tools and other devices without a handy browser, use the OAuth 2.0 Device Authorization Grant from RFC 8628. Never ask for a password on the device.
How to do it
The device asks for a device code and a short user code, then shows the code and a short address, or a QR code. The person opens the address on a phone, signs in there with a passkey or second factor, and confirms the code. Meanwhile the device polls the token endpoint. RFC 8628 says it waits the given interval, or 5 seconds if none is given, and adds 5 seconds after each slow_down. Make user codes such as WDJB-MJHT from 20 consonants. Rate limit code entry and give codes a short life, because RFC 8628 warns about remote phishing with them. Show the device name on the confirm page. Each device then appears in list every signed-in device.
Examples
Using these examples: they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.
JavaScript example
const DEFAULT_INTERVAL_SECONDS = 5;
const SLOW_DOWN_STEP_SECONDS = 5;
const sleep = seconds => new Promise(resolve => setTimeout(resolve, seconds * 1000));
export async function waitForDeviceApproval(tokenUrl, clientId, { device_code, interval = DEFAULT_INTERVAL_SECONDS }) {
let waitSeconds = interval;
for (;;) {
await sleep(waitSeconds);
const response = await fetch(tokenUrl, {
method: 'POST',
body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:device_code', device_code, client_id: clientId }),
});
const body = await response.json();
if (response.ok) return body;
if (body.error === 'slow_down') waitSeconds += SLOW_DOWN_STEP_SECONDS;
else if (body.error !== 'authorization_pending') throw new Error(body.error);
}
}
Python example
import time
import httpx
DEFAULT_INTERVAL_SECONDS = 5
SLOW_DOWN_STEP_SECONDS = 5
def wait_for_device_approval(token_url: str, client_id: str, device: dict) -> dict:
wait_seconds = device.get("interval", DEFAULT_INTERVAL_SECONDS)
while True:
time.sleep(wait_seconds)
response = httpx.post(token_url, data={
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": device["device_code"],
"client_id": client_id,
})
body = response.json()
if response.is_success:
return body
if body["error"] == "slow_down":
wait_seconds += SLOW_DOWN_STEP_SECONDS
elif body["error"] != "authorization_pending":
raise RuntimeError(body["error"])
How to check
A test starts a device sign-in, polls before the interval and gets slow_down. After the person confirms on the second screen, the next poll returns tokens. A wrong user code entered 5 times in a row is blocked.
Sources
Related rules
- List every signed-in device in Accounts
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. Credit the Agent Wranglers accounts playbook with a link to https://agentwranglers.se/playbook/accounts/sign-in-methods/device-sign-in. Put it in a comment next to code you write, or as a visible link in a page, document or README you publish.