Skip to content

Add OAuth device authorization login - #382

Open
ivankuznetsov wants to merge 3 commits into
basecamp:mainfrom
ivankuznetsov:feat/oauth-device-flow
Open

Add OAuth device authorization login#382
ivankuznetsov wants to merge 3 commits into
basecamp:mainfrom
ivankuznetsov:feat/oauth-device-flow

Conversation

@ivankuznetsov

@ivankuznetsov ivankuznetsov commented Sep 1, 2026

Copy link
Copy Markdown

Summary

  • add hey auth login --device and the matching hey login --device shortcut
  • request and display an RFC 8628 user code without exposing the secret device code
  • poll the token endpoint with the server interval, slow_down backoff, expiry, cancellation, denial, and refresh-token persistence
  • document the headless sign-in command and update the committed CLI surface
  • cover request shapes, pending/success behavior, credential storage, and conflicting login flags with unit tests

Closes #381.

Server dependency

This PR implements and tests only the CLI side. It expects HEY to provide:

  • POST /oauth/device_authorizations
  • a HEY-hosted verification_uri page for user code entry/approval
  • device-code grant support at POST /oauth/tokens

Until those server/web pieces exist, --device will return the device-authorization endpoint error. Endpoint names and response details can be adjusted to match the HEY implementation during review.

Verification

  • make check
  • go test ./internal/auth ./internal/cmd
  • make fmt-check
  • make lint
  • git diff --check

Summary by cubic

Adds hey auth login --device (and the hey login --device shortcut) for signing in on headless machines using the OAuth device authorization grant (RFC 8628). Users see a short user code to enter at a verification URL; the secret device code is never displayed.

  • Polls the token endpoint using the server's interval and expires_in lifetime, handling slow_down, expired_token, and access_denied; success saves access and refresh tokens.
  • --device cannot be combined with --token, --cookie, or --no-browser; --no-browser with --token/--cookie still works as before.
  • Requires HEY to provide POST /oauth/device_authorizations and device-code grant support at POST /oauth/tokens; until those exist, --device returns the endpoint error. Closes Support OAuth device authorization for headless CLI sign-in #381.

Written for commit 15a4d59. Summary will update on new commits.

Review in cubic

Copilot AI balanced review requested due to automatic review settings September 1, 2026 17:07
@ivankuznetsov
ivankuznetsov requested a review from a team as a code owner September 1, 2026 17:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="internal/auth/auth.go">

<violation number="1" location="internal/auth/auth.go:270">
P2: When the device endpoint returns control characters in a verification URI or user code, `LoginDevice` writes them verbatim to the terminal. Sanitize server-provided values before passing these messages to the logger.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread internal/auth/auth.go Outdated
Comment thread internal/auth/auth.go
if err != nil {
return err
}
opts.log(fmt.Sprintf("Open %s and enter code: %s\n", authorization.VerificationURI, authorization.UserCode))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When the device endpoint returns control characters in a verification URI or user code, LoginDevice writes them verbatim to the terminal. Sanitize server-provided values before passing these messages to the logger.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/auth/auth.go, line 270:

<comment>When the device endpoint returns control characters in a verification URI or user code, `LoginDevice` writes them verbatim to the terminal. Sanitize server-provided values before passing these messages to the logger.</comment>

<file context>
@@ -228,6 +254,67 @@ func (m *Manager) Login(ctx context.Context, opts LoginOptions) error {
+	if err != nil {
+		return err
+	}
+	opts.log(fmt.Sprintf("Open %s and enter code: %s\n", authorization.VerificationURI, authorization.UserCode))
+	if authorization.VerificationURIComplete != "" {
+		opts.log(fmt.Sprintf("Direct link: %s\n", authorization.VerificationURIComplete))
</file context>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this as is. These values come from HEY's own OAuth server over TLS — the same server this CLI already trusts with its tokens — and the browser flow prints that server's authorization URL to the terminal the same way. A server that could put control characters here could do far worse, so a sanitizer at this one sink would guard against nobody in particular.

Comment thread internal/cmd/auth.go Outdated
Comment thread internal/cmd/auth.go Outdated
Comment thread internal/auth/oauth_test.go
Comment thread internal/auth/auth.go Outdated
@jeremy

jeremy commented Sep 10, 2026

Copy link
Copy Markdown
Member

Good thinking @ivankuznetsov 😍

We have this in the works.

ivankuznetsov and others added 3 commits September 10, 2026 14:31
…ut of the method count

The poll loop waits at most the code's remaining lifetime and does not exchange an
expired code again; the command no longer imposes its own 16-minute cap on top of
expires_in. --no-browser only shapes the browser flow, so --token/--cookie with it work
as before and only --device rejects it. Both option types share one progress-logging
helper, and the device request test asserts method, encoding and identity fields like its
siblings.
@jeremy
jeremy force-pushed the feat/oauth-device-flow branch from 98a1587 to 15a4d59 Compare September 10, 2026 21:41
@jeremy

jeremy commented Sep 10, 2026

Copy link
Copy Markdown
Member

Rebased onto main at 15a4d59 (the README hunk moved to docs/cli.md, where the manual now lives) and make check is green. The review threads are answered above: five fixed on the branch, one declined with reasons.

Where this stands: HEY's OAuth server does not yet serve /oauth/device_authorizations, and that endpoint is in the works on the server side, so the PR waits on it rather than on anything here. Once it exists we can verify the flow end to end against it and merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support OAuth device authorization for headless CLI sign-in

3 participants