Implement SSO integration (SAML/OIDC) #58

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

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
## 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 ```bash # 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 ```yaml # ~/.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
Sign in to join this conversation.