Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
OAuth 2.0 Device Authorization Grant lets a command-line program authenticate without requiring a redirect-capable browser on the same computer. The CLI requests a short-lived device code, displays a verification URL and user code, and polls the authorization server until the user approves the request on a phone or another computer. Use the server-provided expiry and polling interval, handle authorization_pending and slow_down correctly, and store the resulting tokens in the operating system’s credential store.
What OAuth device flow is
OAuth device flow is the OAuth 2.0 Device Authorization Grant defined by RFC 8628, an IETF Standards Track specification published in August 2019. It is designed for Internet-connected clients that cannot use a suitable browser locally or have limited input capabilities. The user reviews and approves the request on a secondary device while the CLI waits for the result.
The protocol is appropriate when a CLI runs on a headless server, a remote shell, a machine without a graphical session, or a device where entering credentials is impractical. It is not a general replacement for browser-based OAuth on capable native applications.
Minimum requirements
- The CLI can make outbound HTTPS requests. Every device-flow request must use TLS.
- The CLI can display or otherwise communicate a verification URI and a user code.
- The user has a phone, computer, or other secondary device for approval.
- The authorization server supports RFC 8628 and has issued the client an identifier.
The device-flow sequence
- Register the client. Create a client registration with the identity provider and obtain a client identifier. A CLI normally cannot keep a client secret confidential, so treat it as a public client.
- Request a device code. Send the
client_idand, when needed, a space-delimitedscopeto the provider’s device authorization endpoint. - Read the response. The server returns a
device_codefor polling, a shorteruser_codefor the human, a verification URI,expires_in, and a recommended pollinginterval. Some providers also return a complete verification URI containing the user code. - Show clear instructions. Print the URI and code in a form that is easy to copy. You may offer to open the user’s browser when a graphical session is available, but the flow must still work when it is not.
- Poll the token endpoint. Submit
grant_type=urn:ietf:params:oauth:grant-type:device_code, thedevice_code, andclient_idat the server-specified interval. - Process the result. Continue on
authorization_pending; increase the delay afterslow_down; stop with a useful message on denial or expiry; and, on success, securely store the access and refresh tokens.
Request and response fields
| Stage | Field | Purpose |
|---|---|---|
| Device request | client_id |
Identifies the registered CLI. |
| Device request | scope |
Requests only the permissions the command actually needs. |
| Device response | device_code |
Opaque value sent to the token endpoint; never show it to the user. |
| Device response | user_code |
Short value the user enters on the verification page. |
| Device response | verification URI | Page where the user signs in and approves the request. |
| Device response | expires_in |
Lifetime of the device authorization, in seconds. |
| Device response | interval |
Minimum number of seconds between token requests. |
| Token response | access_token |
Credential for API calls; protect it like a password. |
| Token response | refresh_token |
Optional long-lived credential used to obtain later access tokens. |
| Token response | token_type and expires_in |
Tell the client how to use and refresh the access token. |
Endpoint URLs, accepted scopes, authentication methods, and response extensions are provider-specific. Use the values documented by the identity provider rather than assuming that another provider’s paths or defaults apply.
#1 Best Overall
- POWERFUL SECURITY KEY: The Security Key C NFC is the essential physical passkey for protecting your digital life from phishing attacks. It ensures only you can access your accounts.
- WORKS WITH 1000+ ACCOUNTS: Compatible with Google, Microsoft, and Apple. A single Security Key C NFC secures 100 of your favorite accounts, including email, password managers, and more.
- FAST & CONVENIENT LOGIN: Plug in your Security Key C NFC via USB-C and tap it, or tap it against your phone (NFC) to authenticate. No batteries, no internet connection, and no extra fees required.
- TRUSTED PASSKEY TECHNOLOGY: Uses the latest passkey standards (FIDO2/WebAuthn & FIDO U2F) but does not support One-Time Passwords. For complex needs, check out the YubiKey 5 Series.
- BUILT TO LAST: Made from tough, waterproof, and crush-resistant materials. Manufactured in Sweden and programmed in the USA with the highest security standards.
A complete Python implementation
The following script uses the standard sequence and handles the protocol’s polling errors. Replace the endpoint and client values with those from your provider. It requires Python 3 and the requests package.
import time
import webbrowser
import requests
DEVICE_ENDPOINT = "https://YOUR_PROVIDER.example/device"
TOKEN_ENDPOINT = "https://YOUR_PROVIDER.example/token"
CLIENT_ID = "YOUR_CLIENT_ID"
SCOPE = "openid profile"
def device_login():
device = requests.post(
DEVICE_ENDPOINT,
data={"client_id": CLIENT_ID, "scope": SCOPE},
timeout=30,
)
device.raise_for_status()
data = device.json()
device_code = data["device_code"]
user_code = data["user_code"]
verification_uri = data.get("verification_uri") or data["verification_url"]
expires_in = int(data["expires_in"])
interval = int(data.get("interval", 5))
print(f"Open: {verification_uri}")
print(f"Enter code: {user_code}")
try:
answer = input("Press Enter to open the browser, or type n to skip: ")
if answer.lower() != "n":
webbrowser.open(verification_uri)
except EOFError:
pass
deadline = time.monotonic() + expires_in
while time.monotonic() < deadline:
time.sleep(interval)
token = requests.post(
TOKEN_ENDPOINT,
data={
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": device_code,
"client_id": CLIENT_ID,
},
timeout=30,
)
if token.status_code == 200:
result = token.json()
if "access_token" not in result:
raise RuntimeError("Token response did not contain access_token")
return result
try:
error = token.json().get("error")
except ValueError:
token.raise_for_status()
raise RuntimeError("Non-JSON error from token endpoint")
if error == "authorization_pending":
continue
if error == "slow_down":
interval += 5
continue
if error in ("access_denied", "expired_token"):
raise RuntimeError(f"Authorization ended: {error}")
raise RuntimeError(f"Token request failed: {error}")
raise TimeoutError("The device code expired before approval")
if __name__ == "__main__":
tokens = device_login()
print("Login succeeded; token type:", tokens.get("token_type", "unknown"))
# Store tokens in the platform credential store, not in this terminal or a log file.
Do not print device_code, access tokens, refresh tokens, or complete authorization responses. In production, replace the final print statement with a call to the operating system's credential manager or a dedicated encrypted store.
Equivalent HTTP requests
cURL
First request a device code:
curl -X POST "https://YOUR_PROVIDER.example/device"
-d "client_id=YOUR_CLIENT_ID"
-d "scope=openid%20profile"
After displaying the returned code and waiting at least the returned interval, poll the token endpoint:
curl -X POST "https://YOUR_PROVIDER.example/token"
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code"
-d "device_code=DEVICE_CODE_FROM_RESPONSE"
-d "client_id=YOUR_CLIENT_ID"
Node.js
This Node.js 18+ example uses the built-in fetch API and follows the same error rules:
Rank #2
- POWERFUL SECURITY KEY: The YubiKey 5C NFC is the most versatile physical passkey, protecting your digital life from phishing attacks. It ensures only you can access your accounts
- WORKS WITH 1000+ ACCOUNTS: Compatible with popular accounts like Google, Microsoft, and Apple. A single YubiKey 5C NFC secures 100+ of your favorite accounts, including email, password managers, and more
- FAST & CONVENIENT LOGIN: Plug in your YubiKey 5C NFC via USB and tap it, or tap it against your phone (NFC), to authenticate. No batteries, no internet connection, and no extra fees required
- MOST SECURE PASSKEY: Supports FIDO2/WebAuthn, FIDO U2F, Yubico OTP, OATH-TOTP/HOTP, Smart card (PIV), and OpenPGP. That means it’s versatile, working almost anywhere you need it
- PRIMARY & SPARE KEYS: Just like having a spare house key, we recommend buying two YubiKeys - one for daily use and one as a spare. That way you’ll never get locked out of your accounts
const deviceEndpoint = 'https://YOUR_PROVIDER.example/device';
const tokenEndpoint = 'https://YOUR_PROVIDER.example/token';
const clientId = 'YOUR_CLIENT_ID';
const deviceBody = new URLSearchParams({ client_id: clientId, scope: 'openid profile' });
const deviceResponse = await fetch(deviceEndpoint, { method: 'POST', body: deviceBody });
if (!deviceResponse.ok) throw new Error(`Device request failed: ${deviceResponse.status}`);
const device = await deviceResponse.json();
console.log(`Open ${device.verification_uri} and enter ${device.user_code}`);
let interval = Number(device.interval || 5);
const deadline = Date.now() + Number(device.expires_in) * 1000;
let tokens;
while (Date.now() < deadline) {
await new Promise(resolve => setTimeout(resolve, interval * 1000));
const body = new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
device_code: device.device_code,
client_id: clientId
});
const response = await fetch(tokenEndpoint, { method: 'POST', body });
const result = await response.json();
if (response.ok) { tokens = result; break; }
if (result.error === 'authorization_pending') continue;
if (result.error === 'slow_down') { interval += 5; continue; }
throw new Error(`Authorization failed: ${result.error}`);
}
if (!tokens) throw new Error('The device code expired before approval');
console.log('Login succeeded');
Provider timing and polling behavior
expires_in and interval are server instructions, not universal constants. For example, current Microsoft Entra device-code documentation uses a default 15-minute sign-in lifetime, while GitHub documents a 900-second validity window for its user code. Your client must honor the values in the response because providers can change them.
GitHub's documented flow asks the user to enter the code at https://github.com/login/device, then polls until authorization completes. GitHub explicitly warns that ignoring its minimum polling interval can trigger rate-limit errors. Sleeping before the first poll, rather than sending requests in a tight loop, avoids unnecessary load and makes the CLI behave consistently on slow networks.
Device flow versus authorization code with PKCE
| Decision factor | Device flow | Authorization code with PKCE |
|---|---|---|
| Browser on CLI host | Not required; approval occurs on a second device. | Normally needs a browser and a redirect back to the application. |
| Redirect channel | None; the CLI polls. | Required, using an app or localhost redirect. |
| User-code exposure | Visible in the terminal and typed on another device, so display it only for the active request. | No device code is typed, but the authorization response must reach the redirect listener. |
| Rate limits | Polling must follow the returned interval; handle slow_down. |
No polling loop, although token and authorization endpoints still have limits. |
| Public clients | Works without a confidential client secret. | PKCE protects the authorization-code exchange and is generally preferred when a capable browser is available. |
| Best fit | Headless hosts, SSH sessions, consoles, and constrained input devices. | Native apps and desktop environments that can open a browser and receive a redirect. |
CLI utilities are generally public clients. GitHub's guidance says authorization code with PKCE is preferable when the primary concern is protecting a client secret; a CLI cannot reliably keep such a secret private. Choose device flow because the browser or redirect channel is unavailable or inconvenient, not as a shortcut around a better browser-based design.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Security and token-storage checklist
- Request the minimum scopes needed for the command.
- Show the client name and requested permissions before the user approves.
- Use HTTPS for every request and reject invalid certificates.
- Never log device codes, user codes, access tokens, refresh tokens, or authorization responses.
- Keep the device code and user code in memory only until the flow ends.
- Store refresh and access tokens in the platform credential store when one is available.
- Provide a logout or revoke command that removes local credentials and uses the provider's revocation mechanism when supported.
- Clear tokens from memory where the language and runtime make that practical.
Troubleshooting common failures
The verification page says the code is invalid
Check that the user entered the current user_code, not the opaque device_code, and that the code has not expired. Request a new device code after expiry rather than retrying the old one.
Rank #3
- POWERFUL SECURITY KEY: The YubiKey 5 NFC is the most versatile physical passkey, protecting your digital life from phishing attacks. It ensures only you can access your accounts
- WORKS WITH 1000+ ACCOUNTS: Compatible with popular accounts like Google, Microsoft, and Apple. A single YubiKey 5 NFC secures 100+ of your favorite accounts, including email, password managers, and more
- FAST & CONVENIENT LOGIN: Plug in your YubiKey 5 NFC via USB and tap it, or tap it against your phone (NFC), to authenticate. No batteries, no internet connection, and no extra fees required
- MOST SECURE PASSKEY: Supports FIDO2/WebAuthn, FIDO U2F, Yubico OTP, OATH-TOTP/HOTP, Smart card (PIV), and OpenPGP. That means it’s versatile, working almost anywhere you need it
- PRIMARY & SPARE KEYS: Just like having a spare house key, we recommend buying two YubiKeys - one for daily use and one as a spare. That way you’ll never get locked out of your accounts
The token endpoint always returns authorization_pending
The user has not completed approval, or the CLI is polling a different tenant or environment than the verification page. Confirm that both endpoints belong to the same provider registration and continue only at the returned interval.
You receive slow_down or HTTP 429
Your client is polling too frequently. Increase the delay, retain that larger interval for subsequent requests, and ensure multiple CLI processes are not sharing one device code.
The request is rejected as an invalid client
Verify the exact client identifier, registration status, tenant, and required content type. Do not add a client secret unless the provider explicitly documents one for this flow.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11The CLI works locally but not over SSH
Remove any assumption that a local browser exists. Always print a copyable verification URI and code, and avoid relying on webbrowser.open for correctness.
Rank #4
- POWERFUL SECURITY KEY: The Security Key NFC is the essential physical passkey for protecting your digital life from phishing attacks. It ensures only you can access your accounts.
- WORKS WITH 1000+ ACCOUNTS: Compatible with Google, Microsoft, and Apple. A single Security Key NFC secures 100 of your favorite accounts, including email, password managers, and more.
- FAST & CONVENIENT LOGIN: Plug in your Security Key NFC via USB-A and tap it, or tap it against your phone (NFC) to authenticate. No batteries, no internet connection, and no extra fees required.
- TRUSTED PASSKEY TECHNOLOGY: Uses the latest passkey standards (FIDO2/WebAuthn & FIDO U2F) but does not support One-Time Passwords. For complex needs, check out the YubiKey 5 Series.
- BUILT TO LAST: Made from tough, waterproof, and crush-resistant materials. Manufactured in Sweden and programmed in the USA with the highest security standards.
Tokens disappear after restart
The implementation is probably storing them only in process memory or a plain temporary file. Use the operating system credential store and handle refresh-token rotation according to the provider's documentation.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Performance, reliability, and operational notes
- Latency: Approval time dominates the flow. A polling interval of several seconds is normal; reducing it rarely improves the user's experience.
- Retries: Retry transient network failures with bounded exponential backoff, but do not bypass the provider's minimum interval.
- Cancellation: Let the user press Ctrl-C to stop polling and clearly state that the device authorization remains valid until its server-provided expiry.
- Clock handling: Use a monotonic local timer for the waiting deadline so a system clock adjustment does not extend the authorization window.
- Concurrency: Associate one polling loop with one device code. Parallel loops can waste rate-limit budget and produce confusing results.
- Cost: The protocol itself does not define a universal per-request price. Any quotas, throttling, or account charges come from the identity provider's plan.
Or skip the browser setup:
If a CLI workflow also needs a screenshot of a consent, documentation, or status page, ScreenshotNeo provides a single HTTPS request instead of requiring you to install and operate a local browser. It is separate from OAuth authentication: use your identity provider for login and ScreenshotNeo for page capture. Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
For the full parameter list, see ScreenshotNeo's API documentation. A direct call looks like this:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
There is a free allowance of 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; Growth is $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is included on every plan. Create a free ScreenshotNeo account to get started.
Implementation takeaway
Device flow is a focused solution for public CLI clients that cannot depend on a local browser. Register the client, request a device code, show the verification instructions, poll at the returned interval, and stop cleanly on denial or expiry. Use PKCE with authorization code flow instead when the application can reliably open a browser and receive a redirect.
Best Value
- The information below is per-pack only
- POWERFUL SECURITY KEY: The Security Key C NFC is the essential physical passkey for protecting your digital life from phishing attacks. It ensures only you can access your accounts.
- WORKS WITH 1000+ ACCOUNTS: Compatible with Google, Microsoft, and Apple. A single Security Key C NFC secures 100 of your favorite accounts, including email, password managers, and more.
- FAST & CONVENIENT LOGIN: Plug in your Security Key C NFC via USB-C and tap it, or tap it against your phone (NFC) to authenticate. No batteries, no internet connection, and no extra fees required.
- TRUSTED PASSKEY TECHNOLOGY: Uses the latest passkey standards (FIDO2/WebAuthn & FIDO U2F) but does not support One-Time Passwords. For complex needs, check out the YubiKey 5 Series.
Frequently Asked Questions
Can a CLI use device flow without any browser at all?
The CLI itself need not include a browser, but the user still needs a secondary device with a browser or provider-approved interface to approve the request.
Is the device code the same as the user code?
No. The user code is entered on the verification page; the device code is an opaque value sent only by the CLI to the token endpoint.
Should a device-flow client use a client secret?
Usually not. CLI utilities are public clients, so a distributed secret cannot be kept confidential. Follow the provider's registration rules and use PKCE when choosing authorization code flow.
What happens if the user never approves the request?
The CLI keeps polling until the server-provided expiry, then reports expiration and requires a new device authorization.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

