Device Authorization Flow
For TVs, consoles and other devices without a keyboard
- Who's involved
- Device, user, auth server
- Best for
- TVs and devices without a keyboard
- Difficulty
- Moderate
1Overview
How it works
The Device Authorization flow (also called the device code flow) lets a device with no browser or easy typing ask for access. It works in three moves:
- The device shows a short
user_codeand a web address. - The user opens that address on their phone or computer and approves the request.
- Meanwhile the device polls (checks in repeatedly) until approval arrives.
2Use cases
When would you use this?
Smart TV apps
You open a streaming app on your TV, see a short code, and approve it on your phone.
Command-line tools
A developer tool in the terminal shows a code and a link to approve in the browser.
Gadgets with no keyboard
A printer, games console or smart display signs in to your account.
Not the right fit? Device has a browser and keyboard? Use Authorization Code with PKCE. It is faster for the user.
3Interactive
Try it step by step
See it step by step
Step 1 of 6
The device asks for a code
The device tells the login server it wants to sign someone in. It gets back a long secret device_code for itself and a short user_code for you.
4Practical advice
Do this, avoid that
Good practices
- Poll at the given
intervalโ Wait at leastintervalseconds, and slow down if told to - Show the code and address clearly โ Make it easy to type and to recognise
- Stop when the code expires โ Start over with a new
device_code - Handle denial โ If the user says no, the server returns
access_denied
Common mistakes
- Polling too fast โ You will be told to
slow_downor be blocked - Showing the
device_codeto the user โ Only theuser_codeis meant to be seen - Using it when a browser is available โ Authorization Code with PKCE is simpler there
- Skipping expiry handling โ Codes usually expire after about 15 minutes
5Code
Code example
Show the code
// Device Authorization flow, device side
const start = await fetch('https://auth.provider.com/oauth/device/code', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ client_id: CLIENT_ID, scope: 'profile' }),
}).then((r) => r.json());
console.log(`Go to ${start.verification_uri} and enter ${start.user_code}`);
let interval = start.interval ?? 5;
while (true) {
await new Promise((r) => setTimeout(r, interval * 1000));
const res = await fetch('https://auth.provider.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
device_code: start.device_code,
client_id: CLIENT_ID,
}),
});
const body = await res.json();
if (body.error === 'authorization_pending') continue;
if (body.error === 'slow_down') { interval += 5; continue; }
if (body.error) throw new Error(body.error); // access_denied or expired_token
return body; // { access_token, ... }
}