Skip to content

Implement SSO integration (SAML/OIDC) #58

@KofTwentyTwo

Description

@KofTwentyTwo

User Story

As an enterprise developer, I want to authenticate using my organization's SSO provider so that I can use qctl without managing separate credentials.

Design

Command Interface

# Configure SSO provider
qctl auth sso configure --provider okta --issuer https://myorg.okta.com

# Login via SSO
qctl auth sso login

# Check SSO status
qctl auth sso status

# Remove SSO configuration
qctl auth sso remove

SSO Configuration Flow

┌─────────────────────────────────────────────────────────────────┐
│                    SSO Configuration                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  qctl auth sso configure                                        │
│       │                                                         │
│       ▼                                                         │
│  ┌─────────────┐     ┌──────────────┐     ┌──────────────────┐ │
│  │ Detect      │────▶│ Fetch OIDC   │────▶│ Store Provider   │ │
│  │ Provider    │     │ Metadata     │     │ Config           │ │
│  └─────────────┘     └──────────────┘     └──────────────────┘ │
│       │                                            │            │
│       ▼                                            ▼            │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ Supported Providers:                                        ││
│  │ - Okta (OIDC)                                               ││
│  │ - Azure AD (OIDC/SAML)                                      ││
│  │ - Google Workspace (OIDC)                                   ││
│  │ - OneLogin (OIDC/SAML)                                      ││
│  │ - Custom OIDC (any compliant provider)                      ││
│  │ - Custom SAML 2.0                                           ││
│  └─────────────────────────────────────────────────────────────┘│
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Output Format

$ qctl auth sso configure --provider okta --issuer https://myorg.okta.com
Fetching OIDC discovery metadata...
Provider: Okta
Issuer: https://myorg.okta.com
Authorization: https://myorg.okta.com/oauth2/v1/authorize
Token: https://myorg.okta.com/oauth2/v1/token

SSO configured successfully.
Run 'qctl auth sso login' to authenticate.

$ qctl auth sso login
Opening browser for SSO authentication...
Waiting for callback...

Authentication successful!
User: james.maes@acme.com
Organization: ACME Corp
Roles: developer, admin
Token expires: 2026-01-04 10:30:00 UTC

$ qctl auth sso status
Provider: Okta
Status: Authenticated
User: james.maes@acme.com
Organization: ACME Corp
Token expires in: 7h 23m

SAML Flow

┌──────┐     ┌─────────┐     ┌─────────┐     ┌────────┐
│ qctl │     │ Browser │     │   IdP   │     │ Voyage │
└──┬───┘     └────┬────┘     └────┬────┘     └───┬────┘
   │              │               │              │
   │ Start SAML   │               │              │
   │──────────────▶               │              │
   │              │  AuthnRequest │              │
   │              │───────────────▶              │
   │              │               │              │
   │              │    Login Page │              │
   │              │◀──────────────│              │
   │              │               │              │
   │              │ User Authenticates           │
   │              │───────────────▶              │
   │              │               │              │
   │              │ SAMLResponse  │              │
   │              │◀──────────────│              │
   │              │               │              │
   │   Callback with assertion    │              │
   │◀─────────────│               │              │
   │              │               │              │
   │         Exchange assertion for token        │
   │─────────────────────────────────────────────▶
   │              │               │              │
   │              Access Token    │              │
   │◀─────────────────────────────────────────────

Configuration File

# ~/.config/qctl/sso.yaml
provider: okta
type: oidc
issuer: https://myorg.okta.com
client_id: 0oa1234567890abcdef
scopes:
  - openid
  - profile
  - email
  - groups
redirect_uri: http://localhost:8400/callback

Files to Create/Modify

File Action Description
qctl-core/src/main/java/io/qrun/qctl/core/auth/sso/SSOCommand.java Create SSO subcommand group
qctl-core/src/main/java/io/qrun/qctl/core/auth/sso/ConfigureCommand.java Create SSO configure command
qctl-core/src/main/java/io/qrun/qctl/core/auth/sso/SSOLoginCommand.java Create SSO login command
qctl-core/src/main/java/io/qrun/qctl/core/auth/sso/StatusCommand.java Create SSO status command
qctl-core/src/main/java/io/qrun/qctl/core/auth/sso/OIDCClient.java Create OIDC protocol implementation
qctl-core/src/main/java/io/qrun/qctl/core/auth/sso/SAMLClient.java Create SAML 2.0 protocol implementation
qctl-core/src/main/java/io/qrun/qctl/core/auth/sso/ProviderConfig.java Create SSO provider configuration
qctl-core/src/main/java/io/qrun/qctl/core/auth/sso/CallbackServer.java Create Local HTTP server for callbacks
qctl-core/src/main/java/io/qrun/qctl/core/auth/sso/ProviderDetector.java Create Auto-detect provider from issuer

Implementation Tasks

  • Create SSOCommand subcommand group under auth
  • Implement OIDC discovery metadata fetching
  • Implement OIDC authorization code flow with PKCE
  • Implement SAML 2.0 SP-initiated flow
  • Create local callback server for browser redirect
  • Add provider auto-detection from issuer URL
  • Implement token exchange with Voyage API
  • Store SSO configuration securely
  • Add group/role claim mapping
  • Handle token refresh for SSO sessions
  • Add timeout and error handling for browser flows
  • Write unit tests for protocol implementations

Acceptance Criteria

  • Can configure Okta as OIDC provider
  • Can configure Azure AD as OIDC/SAML provider
  • Browser opens automatically for authentication
  • Tokens are securely stored after SSO login
  • Group claims are mapped to Voyage roles
  • Token refresh works transparently
  • Clear error messages for SSO failures
  • Works in headless environments with device flow fallback

Metadata

Metadata

Assignees

No one assigned

    Labels

    enterpriseEnterprise featurestoryFeature story linked to epic

    Projects

    Status

    No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions