Add OAuth 2.0 Device Authorization Grant (RFC 8628)#95
Merged
Conversation
The JavaScript provider's credentials object now includes the OAuth2 grant_type (serialised as grant_type in JSON), giving provider scripts visibility into how the authentication was initiated.
Changes:
- JSInputCredentials gains a grantType: GrantTypes field (JSON key
"grant_type") via a CodingKeys mapping
- LoginController derives the grant type from the request mode
(interceptor → .interceptor, otherwise → .authorization_code) and
passes it to the provider
- TokenController password-grant handler passes .password
- All unit tests and test fixtures updated accordingly
E2E (Ham application):
- UserLoginProvider updated to deny interceptor logins by checking
credentials.grant_type and calling commit(false) when it equals
'interceptor'
- New test GrantType/GrantTypeCheck.spec.ts covers both cases:
happy path (authorization_code login succeeds with a code in the
redirect) and error path (interceptor login is denied and the login
page is shown again with a WRONG_CREDENTIALS error)
Implement the full device grant flow for input-constrained clients (CLIs, smart TVs, IoT devices) that cannot open a browser directly.
Core changes:
- Refactor AuthSession into a discriminated union enum (CodeSession, RefreshSession, DeviceSession) replacing the flat struct; backward-compatible with existing Redis-stored sessions via "type" discriminator
- Add DeviceSession with status (pending/authorized/denied), userCode, lastPolledAt for rate limiting, and optional payload
- Add getDevice(byUserCode:) and updateDevice(...) to AuthCodeStorage; Redis impl maintains a secondary "deviceuser~{userCode}" index
- POST /oauth/device_authorization — issues device_code + XXXX-XXXX user_code, stores pending session, respects DeviceGrantConfig
- GET/POST /activate — cross-tenant activation page; supports cookie auto-auth and credential-based auth via JavaScript provider
- POST /token with grant_type=device_code — polls session status, enforces slow_down rate limiting (429), returns token pair on success
- Advertise device_authorization_endpoint in /.well-known/openid-configuration when at least one tenant client supports device_code
- Add device_grant_config to Client CRD schema and Helm template
- Add 4 Prometheus counters: deviceFlowInitiation, deviceFlowAuthorized, deviceFlowPending, deviceFlowSuccess
- Add ACTIVATE.UI translations (en, de, pt) and autofocus username fields
- Fix Traefik Helm values: remove secretResourceNames from rbac (schema removed in latest chart release)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the OAuth 2.0 Device Authorization Grant (RFC 8628) for input-constrained devices such as CLIs, smart TVs, and IoT devices that cannot open a browser directly.
/activateon any browser, logs in, and enters the code/tokenendpoint until the grant is approvedChanges
Refactoring (Phase 1)
AuthSessionrefactored from a flat struct to a discriminated union enum (AuthSession.code,.refresh,.device) — each case carries only fields relevant to its flowAuthSession.CodeTypereplaced byAuthSessionTypeenum throughout the codebase"type": "code"/"type": "refresh") decode without migrationDevice Grant (Phase 2)
POST /oauth/device_authorization— issuesdevice_code,user_code,verification_uri,expires_in,intervalGET /activate— enter user code;POST /activate— authenticate and approvePOST /tokenwithgrant_type=device_code— returnsauthorization_pending(428),slow_down(429),access_denied(400), or tokens (200)DeviceSessioninAuthCodeStorage; secondary Redis keydeviceuser~{userCode}for fast lookup by user codedevice_grant_configfield (expires_in,interval,verification_uri) onClientSpecdevice_authorization_endpointadded to OpenID Connect discovery documentactivate.leaftemplate; translations inen_EN,de_DE,pt_PTdevice_flow_initiation,device_code_authorized,device_code_denied,device_code_expired)device_grant_configschema added tocrd-clients.yamlDeviceGrant.spec.ts+DeviceApptenant/client fixturesTest plan
./tooling.sh test— all unit tests pass./tooling.sh lint— no violationscurl -X POST .../oauth/device_authorization -d 'client_id=...'→ valid JSON response/activate, enter user code, log in → success page/tokenwithdevice_code→ transitions fromauthorization_pendingto200with tokenscurl .../.well-known/openid-configuration→ containsdevice_authorization_endpoint./tooling.sh e2e --filter "DeviceGrant"— all e2e scenarios pass