Quick start
Auth and access
This guide helps you diagnose and resolve common issues with device authorization flow. Learn how to handle errors, debug problems, and implement proper error recovery.
Error: authorization_pending
Description: The user hasn’t completed the authorization process yet.
Solution: Continue polling the token endpoint. This is normal behavior.
// Example: Handle authorization_pendingif (error === "authorization_pending") { console.log("User has not completed authorization yet. Continue polling..."); // Wait for the specified interval before next poll setTimeout(pollForToken, interval * 1000);}Error: slow_down
Description: You’re polling too frequently.
Solution: Increase the polling interval by 5 seconds.
// Example: Handle slow_downif (error === "slow_down") { console.log("Polling too fast. Increasing interval..."); interval += 5; // Increase interval by 5 seconds setTimeout(pollForToken, interval * 1000);}Error: access_denied
Description: The user denied the authorization request.
Solution: Stop polling and inform the user they need to try again.
// Example: Handle access_deniedif (error === "access_denied") { console.log("User denied authorization"); showErrorMessage("Authorization was denied. Please try again."); stopPolling();}Error: expired_token
Description: The device code has expired (typically after 30 minutes).
Solution: Request a new device code.
// Example: Handle expired_tokenif (error === "expired_token") { console.log("Device code expired"); showErrorMessage("This code has expired. Please request a new one."); requestNewDeviceCode();}Problem: Polling more frequently than the recommended interval.
Solution: Always respect the interval value from the device authorization response.
// Good: Respect the intervalfunction pollForToken(deviceCode, interval = 5) { setTimeout(() => { // Make token request checkTokenStatus(deviceCode); }, interval * 1000);}
// Bad: Polling too frequentlyfunction pollForToken(deviceCode) { setInterval(() => { // This polls every 1 second - too frequent! checkTokenStatus(deviceCode); }, 1000);}slow_down properlyProblem: Not increasing the interval when receiving slow_down errors.
Solution: Implement exponential backoff.
let currentInterval = 5; // Start with 5 seconds
function pollForToken(deviceCode) { checkTokenStatus(deviceCode).then((response) => { if (response.error === "slow_down") { currentInterval += 5; // Increase by 5 seconds console.log(`Increasing interval to ${currentInterval} seconds`); }
// Continue polling with updated interval setTimeout(() => pollForToken(deviceCode), currentInterval * 1000); });}Problem: Continuing to poll after receiving fatal errors.
Solution: Stop polling for non-recoverable errors.
function pollForToken(deviceCode) { checkTokenStatus(deviceCode).then((response) => { if (response.error) { switch (response.error) { case "authorization_pending": // Continue polling setTimeout(() => pollForToken(deviceCode), interval * 1000); break; case "slow_down": // Increase interval and continue interval += 5; setTimeout(() => pollForToken(deviceCode), interval * 1000); break; case "access_denied": case "expired_token": // Stop polling - these are fatal errors stopPolling(); handleError(response.error); break; } } else { // Success - stop polling handleSuccess(response); } });}Scopes for the device authorization flow must be passed to the /oauth2/v2/device_authorization endpoint when the device code is requested — not to the /oauth2/token endpoint during polling. Passing scopes to the token endpoint is not supported and will be ignored or cause an error.
# Correct: scopes on the device authorization endpointcurl -X POST https://<your-subdomain>.kinde.com/oauth2/v2/device_authorization \ --data-urlencode "client_id=<YOUR_CLIENT_ID>" \ --data-urlencode "scope=openid profile email offline"In Kinde’s device authorization flow, exchanging an existing access token for a new access token or ID token via the token endpoint is not supported. Once you have an access token from the device flow, use it directly with your API. If you need a new token, restart the device authorization flow to obtain a fresh device code and complete the authorization process again.
Problem: Network requests timing out.
Solution: Implement proper timeout handling and retry logic.
function checkTokenStatus(deviceCode) { return fetch("https://<your-subdomain>.kinde.com/oauth2/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "urn:ietf:params:oauth:grant-type:device_code", client_id: "<YOUR_CLIENT_ID>", device_code: deviceCode }), timeout: 10000 // 10 second timeout }) .then((response) => response.json()) .catch((error) => { console.error("Network error:", error); // Retry after a delay setTimeout(() => checkTokenStatus(deviceCode), 5000); });}Problem: Cannot resolve the Kinde domain.
Solution: Verify your domain configuration and network connectivity.
# Test DNS resolutionnslookup <your-subdomain>.kinde.com
# Test connectivitycurl -I https://<your-subdomain>.kinde.com/oauth2/v2/device_authorization