Skip to main content
Version: Latest (4.0.6)

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 NameDescription
client_idClient which represents the business-App in cidaas
device_codeCode used to poll the token endpoint. After authorization, this code becomes invalid
user_codeShort code that the user enters on the verification page (typically 6-8 characters)
verification_uriURL where the user can enter the user_code and credentials. The user visits this URL on a separate device with a browser
verification_uri_completeOptional: 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
intervalTime period (in seconds) that the device should wait between polling requests to the token endpoint
expires_inExpiration time (in seconds) for the user_code. After this time, the user code expires and a new device authorization request is needed
grant_typeMust 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_code when 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_code expires (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 stateWhat 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 requiredLogin (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.

Security Note

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.

When to Use Device Code Flow

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_secret on 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)

  1. Device calls POST /authz-srv/device/authz → user sees user_code + verification URI.
  2. User opens GET /token-srv/device/verify?client_id=…&user_code=… (or your themed device_init_code URL with the same query parameters).
  3. On valid code → redirect to /authz-srv/authz?... (response_type=token, redirect_uri = configured device_success_page URL).
  4. Authz completes (login only if needed) → redirect to device_success_page.
  5. Device keeps polling POST /token-srv/token with grant_type=urn:ietf:params:oauth:grant-type:device_code until 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:

ParameterWhen it appearsExample / notes
client_idInvalid or expired user codeDevice app client ID
user_codeUnknown code (may be preserved from the request)Code the user entered
errorInvalid or malformed codeinvalid_request: invalid user_code
error_codeInvalid or expired code507
error_descriptionExpired codee.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:

ParameterExample
stateValue from the device authorization request
erroraccess_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.

Multiple devices and session renewal

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​

APIDescriptionLink to API
Step 1: Initiate Device AuthorizationThis call is used to initialize the device authorization request and obtain the device_code, user_code, and verification_uriView 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 expiresView API
Step 9: Token IntrospectionValidate 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 interval specified in the response. If the user hasn't authenticated yet, the server will return authorization_pending. Once the user completes authentication, the polling request will return the access token.

info
Need Support?

Please contact us directly on our support page or reach out to cidaas support at [email protected].