Device Code Flow
The Device Code Flow (officially: OAuth 2.0 Device Authorization Grant) is an OAuth 2.0 grant type designed for devices that don't have a browser or have limited input capabilities, such as smart TVs, gaming consoles, CLI tools, or IoT devices. It enables users to authorize these devices to access their resources by authenticating on a separate device (e.g., smartphone or computer) with a full browser.
This flow is ideal when the device cannot securely input credentials or display a full login page, but the user has access to another device with a browser.
How it Works
Before we step into how it works, let's understand some useful parameters.
| Parameter Name | Description |
|---|---|
client_id | Client which represents the business-App in cidaas |
device_code | Code used to poll the token endpoint. After authorization, this code becomes invalid |
user_code | Short code that the user enters on the verification page (typically 6-8 characters) |
verification_uri | URL where the user can enter the user_code and credentials. The user visits this URL on a separate device with a browser |
verification_uri_complete | Optional: Complete URL that includes the user_code as a query parameter: <verification_uri>?user_code=<user_code>. This allows direct access without manual code entry |
interval | Time period (in seconds) that the device should wait between polling requests to the token endpoint |
expires_in | Expiration time (in seconds) for the user_code. After this time, the user code expires and a new device authorization request is needed |
grant_type | Must be urn:ietf:params:oauth:grant-type:device_code when exchanging the device code for a token |
Step 1: The device application makes a POST request to the device authorization endpoint with client_id (and optionally scope) to initiate the device authorization flow.
Step 2: cidaas responds with device_code, user_code, verification_uri, verification_uri_complete (optional), interval, and expires_in.
Step 3: The device displays the user_code and verification_uri (or verification_uri_complete) on its screen. If possible, display a QR code containing the verification_uri_complete URL. The device_code is kept by the device application and used for polling (not shown to the user).
The device instructs the user to:
- Visit the verification URL on a separate device (smartphone, computer, etc.) with a browser
- Enter the
user_codewhen prompted - Complete the authentication on that device
Step 4: While the user is authenticating, the device application starts polling the token endpoint at the specified interval using the device_code. The polling continues until:
- The user successfully authenticates (polling returns tokens)
- The
user_codeexpires (polling returns an error) - A maximum number of polling attempts is reached
Step 5: The user visits the verification URL on their separate device (smartphone, computer, etc.) and enters the user_code. If a QR code was provided, the user can scan it to automatically open the verification page with the code pre-filled.
Step 6 (browser / hosted pages only): After the user submits a valid user_code, cidaas redirects the browser to /authz-srv/authz (same authorize endpoint as other OAuth flows). From here, behavior matches a normal authorize request:
| Browser state | What the user sees |
|---|---|
Valid cidaas session cookie (session still acceptable for max_age, and no forced re-login such as prompt=login) | Login hosted page is skipped (SSO). The browser goes straight to device_success_page. |
| No session, expired session, or login required | Login (and MFA/consent hosted pages if configured), then redirect to device_success_page. |
If you use hosted pages, Step 6 in the diagram means “authenticate when required,” not “always show login.”
Step 7: After successful authentication, the user's authorization is linked to the device_code. The constrained device receives tokens from token endpoint polling (Step 4), not from the browser redirect. The polling response contains access_token, token_type (typically Bearer), expires_in, and optionally refresh_token and scope. The device stops polling.
Step 8: The device application uses the access_token in the Authorization header to access protected resources on the resource server.
Step 9: The resource server validates the access_token using one of the following methods:
- A: Token introspection: Make an introspection call to cidaas to validate the token's authenticity and obtain information about the token (scopes, expiration, user, etc.).
- B: Offline validation: Validate the token's signature and check that the expiration time is in the future (for JWT tokens).
Step 10: If the token is valid, the resource server responds with the requested resource.
Important: The Device Code Flow is designed for public clients (devices without secure storage). No client_secret is required, making it suitable for devices that cannot securely store credentials. The device_code should be kept secure by the device application and never exposed to users.
Use this flow when:
- Your device has limited or no input capabilities (no keyboard, limited display)
- Your device cannot securely display a full login page
- Users have access to a separate device with a browser (smartphone, computer)
- You're building for Smart TVs, gaming consoles, IoT devices, or CLI tools
- You cannot securely store a
client_secreton the device
If your device has full browser capabilities, consider using the PKCE Flow instead.
Hosted pages: device_init_code and device_success_page
Device apps use hosted page IDs device_init_code (enter or confirm the user code) and device_success_page (confirmation in the browser after authorization). They are resolved from the app’s hosted pages group.
Browser path (summary)
- Device calls
POST /authz-srv/device/authz→ user seesuser_code+ verification URI. - User opens
GET /token-srv/device/verify?client_id=…&user_code=…(or your themeddevice_init_codeURL with the same query parameters). - On valid code → redirect to
/authz-srv/authz?...(response_type=token,redirect_uri= configureddevice_success_pageURL). - Authz completes (login only if needed) → redirect to
device_success_page. - Device keeps polling
POST /token-srv/tokenwithgrant_type=urn:ietf:params:oauth:grant-type:device_codeuntil tokens are issued or the code expires.
Query parameters on redirects
Successful authorization → device_success_page (Device application type)
For Device clients, cidaas redirects to the hosted device_success_page URL without OAuth token parameters in the query string or fragment. Access and refresh tokens are returned only to the device via the device-code token poll. This avoids exposing tokens in the browser URL.
Do not read access_token, id_token, or state from the success page URL for integration logic; rely on polling on the device.
User-code errors → device_init_code
When verification fails or the code expired, the browser is redirected back to device_init_code with query parameters such as:
| Parameter | When it appears | Example / notes |
|---|---|---|
client_id | Invalid or expired user code | Device app client ID |
user_code | Unknown code (may be preserved from the request) | Code the user entered |
error | Invalid or malformed code | invalid_request: invalid user_code |
error_code | Invalid or expired code | 507 |
error_description | Expired code | e.g. given code is invalid or expired |
Other query parameters from the incoming verify request (for example ui_locales) may be preserved.
Authorization denied / cancel → device_success_page
If the user cancels login or access is denied, the redirect to device_success_page may include OAuth error parameters, for example:
| Parameter | Example |
|---|---|
state | Value from the device authorization request |
error | access_denied |
Login and MFA errors
Failures during login, MFA, or consent use the corresponding hosted pages (login, MFA, consent, error pages) for that app—the same as other OAuth authorize flows—not device_success_page.
Device-side polling errors (invalid device_code, user slow to authorize, denial) use standard OAuth token errors such as authorization_pending, expired_token, and access_denied on POST /token-srv/token; handle these in the device app, not on hosted pages.
See also Device Success hosted page.
If the user starts or renews a Device Code authorization on a second device while a session from an earlier Device Code flow is still active on the first device, cidaas does not end the first device's session. Each successful Device Code exchange keeps its own session; renewing on another device does not invalidate the first.
Video Tutorial
Let's look at a simple tutorial to get an idea of the Device Code Flow.
Technical Integration
| API | Description | Link to API |
|---|---|---|
| Step 1: Initiate Device Authorization | This call is used to initialize the device authorization request and obtain the device_code, user_code, and verification_uri | View API |
| Step 7: Exchange Device Code (Polling) | This API must be called repeatedly at the specified interval using the device_code until the user completes authentication or the code expires | View API |
| Step 9: Token Introspection | Validate and get information about an access token (optional) | View API |
Example Implementation
Here's a complete example of implementing the Device Code Flow:
Step 1: Initiate Device Authorization
POST https://{{base_url}}/authz-srv/device/authz
Content-Type: application/x-www-form-urlencoded
client_id={{client_id}}
&scope={{desired_scopes}}
Response:
{
"device_code": "abc123...",
"user_code": "ABC-DEF",
"verification_uri": "https://your-domain.cidaas.de/device",
"verification_uri_complete": "https://your-domain.cidaas.de/device?user_code=ABC-DEF",
"interval": 5,
"expires_in": 1800
}
Step 3-4: Display User Code and Start Polling
// Display user_code and verification_uri to the user
console.log(`Visit: ${response.verification_uri}`);
console.log(`Enter code: ${response.user_code}`);
// Start polling at the specified interval
const pollForToken = async () => {
while (true) {
await sleep(response.interval * 1000);
const tokenResponse = await fetch('https://{{base_url}}/token-srv/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: response.device_code,
client_id: '{{client_id}}'
})
});
const data = await tokenResponse.json();
if (tokenResponse.ok) {
// User has authenticated, we have tokens!
return data.access_token;
} else if (data.error === 'authorization_pending') {
// User hasn't authenticated yet, continue polling
continue;
} else {
// Error occurred (expired, denied, etc.)
throw new Error(data.error);
}
}
};
Step 7: Use Access Token
GET https://{{resource_server}}/api/protected-resource
Authorization: Bearer {{access_token}}
Note: The device should poll at the exact
intervalspecified in the response. If the user hasn't authenticated yet, the server will returnauthorization_pending. Once the user completes authentication, the polling request will return the access token.
Please contact us directly on our support page or reach out to cidaas support at [email protected].