Device-flow fallback for browserless logins (SSH, headless) - #33
Closed
jordanalexmeyer wants to merge 1 commit into
Closed
Device-flow fallback for browserless logins (SSH, headless)#33jordanalexmeyer wants to merge 1 commit into
jordanalexmeyer wants to merge 1 commit into
Conversation
The loopback flow cannot work over SSH or on displayless hosts: the browser opens elsewhere (or not at all) and the 127.0.0.1 redirect never comes back. Detect that and switch to the RFC 8628 device flow — print a one-time code and URL, approve from a browser on any device, poll the token endpoint for the result. --device forces the flow explicitly. The endpoint comes only from discovery metadata (RFC 8628 has no conventional path); servers that do not advertise it keep the loopback behavior.
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.
Stacked on #32 (WorkOS Connect login adaptation).
Summary
extend loginnow automatically falls back to the OAuth device authorization flow (RFC 8628) when no usable local browser is detected: in an SSH session (SSH_CONNECTION/SSH_TTY), or on a Linux host with noDISPLAY/WAYLAND_DISPLAY. The loopback flow cannot work there — the browser would open on the wrong machine and the127.0.0.1redirect could never come back.--deviceflag forces the flow explicitly: the CLI prints a one-time code and a verification URL (usingverification_uri_completewhen the server provides it, so the code is prefilled), the user approves from a browser on any device, and the CLI polls the token endpoint until approval.gh auth login), AWS SSO, and Docker do for their browserless paths, while keeping loopback + PKCE as the default for desktop use (the gcloud/Azure/Heroku camp).Implementation
internal/oauth/discovery.go:device_authorization_endpointis now consumed from RFC 8414 metadata, host-pinned like the other endpoints. It is not given a hardcoded default — RFC 8628 defines no conventional path — so servers that don't advertise it simply keep the loopback-only behavior (including the auto-fallback case, which then prints the loopback URL as before).internal/oauth/device.go(new):DeviceAuthorize(sendsclient_id+ RFC 8707resource) andPollDeviceToken(handlesauthorization_pending, backs off onslow_down, stops on terminal errors and onexpires_in). User-visible strings from the server (user_code, verification URIs) are terminal-sanitized at the source.internal/cli/login.go: loopback and device flows extracted into separate functions sharing the same completion path (store record, sid-aware revocation of the replaced grant,/mepersonalization). Friendly errors foraccess_denied/expired_token/ timeout. Browser detection is injectable for tests.oauth.Records, so refresh,whoami, andlogout(/oauth/revoke-current) work unchanged for device-flow logins.Verification
go test ./internal/oauth/ ./internal/cli/green locally (new coverage: device client unit tests incl. slow_down/expiry/denial/sanitization; CLI tests for--device, auto-fallback, loopback preserved when the server lacks the endpoint, unsupported-server error, detection heuristics).✓ Signed in. Also verified: WorkOS enables the device grant only for manually registered (dashboard/API) Connect apps; dynamically registered (DCR) apps getunauthorized_client: Device authorization is not enabled for this application.Live testing surfaced three WorkOS-side gaps that make device-grant tokens unusable against our API today (none are client-side; this PR's code is correct per the RFCs and ready for when WorkOS fixes them):
resource: tokens are minted withaud = <environment client id>instead of the requested resource, so the API's audience-lane check rejects them (TOKEN_INVALID, fail-closed as designed). The authorization-code grant honorsresourcecorrectly./deviceredirects signed-out users to the bare external Login URI with noexternal_auth_idand no return URL, so the login bridge cannot resume the flow. Works only when the browser already holds an AuthKit session.offline_access, which is also needed because device-grant responses carry norefresh_tokenand a 300s access token).The auto-fallback and
--deviceremain safe to merge: against servers that don't advertise the device endpoint the CLI keeps today's loopback behavior, and against WorkOS the failure is a clean error rather than a hang.Manual step required
WorkOS enables the device grant per application, but only for manually registered Connect apps — there is no toggle. If an environment's CLI app was created via DCR, recreate it in the dashboard or via
POST /connect/applications. Documented in MANUAL-STEPS.md (plans repo) along with the V8 support-ticket text.