Implement OIDC device flow authentication #54

Open
opened 2026-01-03 23:46:59 +00:00 by KofTwentyTwo · 0 comments
KofTwentyTwo commented 2026-01-03 23:46:59 +00:00 (Migrated from github.com)

User Story

As a developer, I want to authenticate with qctl using my browser so I can securely access Voyage APIs without managing passwords in the CLI.

Design

Command Interface

# Start login flow
qctl auth login

# Login to specific provider
qctl auth login --provider voyage

# Check authentication status
qctl auth status

# Logout (clear tokens)
qctl auth logout

# Refresh token manually
qctl auth refresh

Device Flow Output

Logging in to Voyage...

Please visit: https://voyage.qrun.io/device
Enter code: ABCD-1234

Waiting for authorization... (press Ctrl+C to cancel)

[Browser opens automatically]

Authenticated as: james.maes@example.com
Organization: acme-corp
Token stored in: macOS Keychain

Login successful!

OIDC Configuration

# ~/.qctl/qctl.yaml
auth:
  provider: voyage
  providers:
    voyage:
      issuer: https://auth.qrun.io
      client_id: qctl-cli
      scopes:
        - openid
        - profile
        - qctl:read
        - qctl:write
      device_auth_endpoint: /oauth/device/code
      token_endpoint: /oauth/token

Token Storage

# Tokens stored in OS keychain with entries:
qctl:voyage:access_token
qctl:voyage:refresh_token
qctl:voyage:id_token
qctl:voyage:expires_at

Files to Create/Modify

File Action Purpose
qctl-core/src/main/java/io/qrun/qctl/core/auth/AuthCommand.java Create Auth command group
qctl-core/src/main/java/io/qrun/qctl/core/auth/LoginCommand.java Create Login subcommand
qctl-core/src/main/java/io/qrun/qctl/core/auth/LogoutCommand.java Create Logout subcommand
qctl-core/src/main/java/io/qrun/qctl/core/auth/StatusCommand.java Create Status subcommand
qctl-core/src/main/java/io/qrun/qctl/core/auth/DeviceFlow.java Create Device flow implementation
qctl-core/src/main/java/io/qrun/qctl/core/auth/TokenManager.java Create Token lifecycle
qctl-core/src/main/java/io/qrun/qctl/core/auth/OIDCClient.java Create OIDC HTTP client
qctl-core/src/main/java/io/qrun/qctl/core/auth/model/DeviceCode.java Create Device code response
qctl-core/src/main/java/io/qrun/qctl/core/auth/model/TokenResponse.java Create Token response

Implementation Tasks

  • Create AuthCommand with login, logout, status, refresh subcommands
  • Implement DeviceFlow with RFC 8628 compliance
  • Request device code from authorization server
  • Display user code and verification URI
  • Open browser automatically (with fallback)
  • Poll token endpoint with appropriate interval
  • Handle authorization_pending, slow_down, expired_token errors
  • Implement TokenManager for token storage/retrieval
  • Integrate with OS keychain (#57)
  • Implement automatic token refresh before expiry
  • Add --provider flag for multi-provider support
  • Write unit tests for DeviceFlow
  • Write integration tests with mock OIDC server

Acceptance Criteria

  • qctl auth login starts device authorization flow
  • Displays verification URI and user code
  • Opens browser automatically
  • Polls for token with backoff
  • Stores tokens securely in keychain
  • qctl auth status shows current auth state
  • qctl auth logout clears stored tokens
  • Automatic token refresh works silently
  • Works with Voyage OIDC provider
## User Story As a developer, I want to authenticate with qctl using my browser so I can securely access Voyage APIs without managing passwords in the CLI. ## Design ### Command Interface ```bash # Start login flow qctl auth login # Login to specific provider qctl auth login --provider voyage # Check authentication status qctl auth status # Logout (clear tokens) qctl auth logout # Refresh token manually qctl auth refresh ``` ### Device Flow Output ``` Logging in to Voyage... Please visit: https://voyage.qrun.io/device Enter code: ABCD-1234 Waiting for authorization... (press Ctrl+C to cancel) [Browser opens automatically] Authenticated as: james.maes@example.com Organization: acme-corp Token stored in: macOS Keychain Login successful! ``` ### OIDC Configuration ```yaml # ~/.qctl/qctl.yaml auth: provider: voyage providers: voyage: issuer: https://auth.qrun.io client_id: qctl-cli scopes: - openid - profile - qctl:read - qctl:write device_auth_endpoint: /oauth/device/code token_endpoint: /oauth/token ``` ### Token Storage ``` # Tokens stored in OS keychain with entries: qctl:voyage:access_token qctl:voyage:refresh_token qctl:voyage:id_token qctl:voyage:expires_at ``` ## Files to Create/Modify | File | Action | Purpose | |------|--------|---------| | `qctl-core/src/main/java/io/qrun/qctl/core/auth/AuthCommand.java` | Create | Auth command group | | `qctl-core/src/main/java/io/qrun/qctl/core/auth/LoginCommand.java` | Create | Login subcommand | | `qctl-core/src/main/java/io/qrun/qctl/core/auth/LogoutCommand.java` | Create | Logout subcommand | | `qctl-core/src/main/java/io/qrun/qctl/core/auth/StatusCommand.java` | Create | Status subcommand | | `qctl-core/src/main/java/io/qrun/qctl/core/auth/DeviceFlow.java` | Create | Device flow implementation | | `qctl-core/src/main/java/io/qrun/qctl/core/auth/TokenManager.java` | Create | Token lifecycle | | `qctl-core/src/main/java/io/qrun/qctl/core/auth/OIDCClient.java` | Create | OIDC HTTP client | | `qctl-core/src/main/java/io/qrun/qctl/core/auth/model/DeviceCode.java` | Create | Device code response | | `qctl-core/src/main/java/io/qrun/qctl/core/auth/model/TokenResponse.java` | Create | Token response | ## Implementation Tasks - [ ] Create AuthCommand with login, logout, status, refresh subcommands - [ ] Implement DeviceFlow with RFC 8628 compliance - [ ] Request device code from authorization server - [ ] Display user code and verification URI - [ ] Open browser automatically (with fallback) - [ ] Poll token endpoint with appropriate interval - [ ] Handle authorization_pending, slow_down, expired_token errors - [ ] Implement TokenManager for token storage/retrieval - [ ] Integrate with OS keychain (#57) - [ ] Implement automatic token refresh before expiry - [ ] Add --provider flag for multi-provider support - [ ] Write unit tests for DeviceFlow - [ ] Write integration tests with mock OIDC server ## Acceptance Criteria - [ ] `qctl auth login` starts device authorization flow - [ ] Displays verification URI and user code - [ ] Opens browser automatically - [ ] Polls for token with backoff - [ ] Stores tokens securely in keychain - [ ] `qctl auth status` shows current auth state - [ ] `qctl auth logout` clears stored tokens - [ ] Automatic token refresh works silently - [ ] Works with Voyage OIDC provider
Sign in to join this conversation.