AUTH4D-16 native device authorization

The device flow follows RFC 8628. A native client is identified by both the tenant and client IDs; all one-time state is held in the tenant’s SQLite-backed Durable Object. Device codes are opaque and user codes are hashed before storage.

Start a device authorization

POST /device/authorization?tenant_id={tenantId} uses application/x-www-form-urlencoded with client_id and scope. The client must be enabled, have the native type, and allow the device-code grant. openid is required. offline_access is accepted only when the client also allows the refresh-token grant.

The response is DeviceAuthorizationResponse. Its verification URL is a confirmation page that shows the registered application, the requested scopes, and the user code. Opening the URL never approves the device. It includes tenant_id, client_id, and user_code in the complete URL so a browser can show the exact application and code.

Verify, approve, or deny

GET /device/verify?tenant_id={tenantId}&client_id={clientId}&user_code={userCode} displays the application and code and asks the user to approve or deny. Approval and denial are explicit button actions. Both require an authenticated browser session, an exact same-origin Origin, and the page’s per-flow CSRF cookie and X-CSRF-Token header.

POST /device/approval?tenant_id={tenantId} accepts a JSON DeviceApprovalRequest and returns {"status":"approved"}. POST /device/denial?tenant_id={tenantId} accepts the same request shape and returns {"status":"denied"}. The browser flow ID binds the request to the verification page. The caller cannot change scopes: approval grants exactly the scopes stored when the device request was created. Approval persists consent, including offline_access when requested.

Poll and exchange

POST /oauth/token?tenant_id={tenantId} accepts an application/x-www-form-urlencoded DeviceCodeTokenRequest and returns TokenResponse on success. Pending requests return authorization_pending; polling before the required interval returns slow_down and increases the required interval by five seconds. A denial returns access_denied, expiry returns expired_token, and an approved device code can be exchanged once. Tenant and client identity are part of every device-state lookup, so another tenant or client cannot approve or redeem the code.

The token response is issued by the shared session service. It includes a refresh token only when offline_access was explicitly approved and stored in consent.

Source: docs/api-spec/auth4d-16.md