Skip to content

OAuth2 Device Flow

A device requests codes with a form POST to /oauth2/device_authorization. A public client supplies client_id; a confidential client uses its registered authentication method. Supplying a secret for a public client is rejected instead of being ignored. The response always includes a verification URI, complete verification URI, positive expiry, and positive polling interval, with no-store/no-cache headers.

This implementation keeps Device Code as an OAuth2 authorization flow. It rejects the openid scope with invalid_scope and therefore does not issue an OIDC ID token through this grant. Zero Auth Lite demonstrates OIDC identity through the Authorization Code flow instead.

With the built-in UI enabled, GET /oauth2/device/verify authenticates the browser when necessary and displays the device page. The user approves or denies a pending request with a form POST to the same path. The POST requires the browser session plus configured CSRF header or hidden-form protection. These presentation routes remain visible in the application OpenAPI document under the device-flow tag, but they are built-in HTML adapters rather than OAuth2 protocol endpoints. Device authorization and token polling remain the protocol endpoints.

With external OAuth2 interaction enabled, the Device Authorization response uses the configured frontend URL as verification_uri. The authenticated frontend reads the pending interaction and records the user's decision through the versioned Device interaction API. The external authentication UI guide documents that JSON exchange. Settings validation requires either the built-in or external interaction mode so a client cannot receive an unusable verification URI.

The decision is organization-bound and one-time. Invalid, expired, or already completed codes return a 400 HTML response; another user cannot overwrite a completed decision.

The device polls /oauth2/token with the device-code grant. Polling before a decision returns authorization_pending. Polling too quickly returns slow_down and increases the required interval by five seconds. Denial returns access_denied, expiry returns expired_token, and an approved device code can issue tokens only once.