Skip to content

Feat/cli open id connect#987

Open
webskin wants to merge 2 commits intoMAIF:mainfrom
webskin:feat/cli-open-id-connect
Open

Feat/cli open id connect#987
webskin wants to merge 2 commits intoMAIF:mainfrom
webskin:feat/cli-open-id-connect

Conversation

@webskin
Copy link

@webskin webskin commented Dec 19, 2025

Pull Request: CLI Authentication via OpenID Connect

Summary

This PR adds support for CLI OIDC authentication, enabling command-line tools (like the Izanami Go CLI -> https://github.com/webskin/izanami-go-cli/releases) to authenticate users through an OpenID Connect provider using a browser-based flow with state-based token polling.

Motivation

CLI tools need a secure way to authenticate users via OIDC without requiring:

  • A local HTTP callback server (complex firewall/NAT issues)
  • Manual copy-paste of tokens (error-prone, poor UX)

This implementation uses a polling-based approach where the CLI opens a browser for authentication and polls the server for the resulting token.

Changes

New Files:

File Description
CliAuthDatastore.scala Pluggable storage abstraction with factory pattern
InMemoryCliAuthStorage.scala Thread-safe storage with automatic cleanup for single-instance deployments
PostgresCliAuthStorage.scala Database-backed storage for load-balanced/clustered setups
CliAuthentication.scala Models for PendingCliAuth and CompletedCliAuth with configurable TTLs
V22__create_cli_auth_table.sql PostgreSQL migration for CLI auth tables

Modified Files:

File Changes
LoginController.scala Added cliOpenIdConnect and cliTokenPoll endpoints
Errors.scala Added CLI-specific error types
login.tsx Enhanced to detect CLI auth and show completion message
application.conf Added IZANAMI_CLI_AUTH_STORAGE configuration

New API Endpoints

Endpoint Method Description
/api/admin/cli-login?state={state} GET Initiate CLI OIDC flow, returns redirect to OIDC provider
/api/admin/cli-token?state={state} GET Poll for token after authentication

Token Polling Responses:

  • 200 OK - Authentication complete, returns JWT token
  • 202 Accepted - Authentication pending, keep polling
  • 404 Not Found - Invalid or unknown state
  • 410 Gone - Token expired (already claimed or TTL exceeded)
  • 429 Too Many Requests - Rate limited, includes Retry-After header

Authentication Flow

┌─────────┐                    ┌─────────────┐                    ┌──────────────┐
│   CLI   │                    │   Izanami   │                    │ OIDC Provider│
└────┬────┘                    └──────┬──────┘                    └──────┬───────┘
     │ 1. Generate state              │                                  │
     │ 2. GET /cli-login?state=...    │                                  │
     │ ───────────────────────────>   │                                  │
     │ <───────────────────────────   │ 3. Store pending auth            │
     │    302 Redirect to OIDC        │                                  │
     │ 4. Open browser ───────────────┼──────────────────────────────>   │
     │ 5. Start polling               │    6. User authenticates         │
     │ ───────────────────────────>   │ <─────────────────────────────   │
     │    GET /cli-token?state=...    │    Callback with code + state    │
     │ <───────────────────────────   │ 7. Exchange code, store token    │
     │    202 (pending) / 200 (ready) │                                  │
     │ 8. Receive JWT token           │ 9. Delete token (single-use)     │
     └────────────────────────────────┴──────────────────────────────────┘

Security Features

Feature Implementation
State Entropy 256-bit random state prevents guessing attacks
CSRF Protection State parameter passed through OIDC flow with cli: prefix
Single-Use Tokens Token deleted from server after first retrieval
Rate Limiting 60 poll requests per minute per state
Short TTLs 5 min for pending auth, 2 min for completed token pickup
PKCE Support Uses PKCE with OIDC provider when available

Configuration

Variable Values Default Description
IZANAMI_CLI_AUTH_STORAGE in-memory, postgresql in-memory Storage backend for CLI auth state
  • in-memory: Suitable for single-instance deployments, uses thread-safe concurrent maps with scheduled cleanup
  • postgresql: Required for clustered/load-balanced deployments, stores state in database

Testing

  • Added LoginAPISpec tests for CLI OIDC endpoints
  • Fixed existing OIDC callback tests to include state parameter

CLI Usage (izanami-go-cli)

# OIDC login - opens browser, polls for token automatically
iz login --oidc --url https://izanami.example.com

# With custom timeout
iz login --oidc --url https://izanami.example.com --timeout 10m

# Headless mode (print URL, don't open browser)
iz login --oidc --url https://izanami.example.com --no-browser

Breaking Changes

None. This is a purely additive feature.

Checklist

  • New endpoints documented in routes
  • PostgreSQL migration included
  • In-memory storage for development/single-instance
  • PostgreSQL storage for production clusters
  • Rate limiting implemented
  • Security measures (CSRF, single-use, short TTLs)
  • Frontend updated for CLI auth completion
  • Tests added for new endpoints

webskin and others added 2 commits December 18, 2025 21:22
Implement CLI OIDC authentication that enables command-line tools to
authenticate users through an OpenID Connect provider using a browser-based
flow with state-based token polling.

Key features:
- CLI initiates auth via /api/admin/cli-login, user completes flow in browser
- CLI polls /api/admin/cli-token to retrieve JWT after successful auth
- No local HTTP server required on CLI side (unlike GitHub CLI approach)
- Supports both single-instance (in-memory) and clustered (PostgreSQL) deployments

New components:
- CliAuthDatastore: pluggable storage abstraction with factory pattern
- InMemoryCliAuthStorage: thread-safe storage with automatic cleanup
- PostgresCliAuthStorage: database-backed storage for load-balanced setups
- PendingCliAuth/CompletedCliAuth models with configurable TTLs

Security measures:
- CSRF protection via state parameter (cli: prefix passed through OIDC)
- PKCE support for public clients
- Rate limiting: 60 polls/minute per state
- Single-use tokens deleted after claim
- Short TTLs: 5min pending, 2min completed

API endpoints:
- GET /api/admin/cli-login?state={state} - initiate CLI auth flow
- GET /api/admin/cli-token?state={state} - poll for token (200/202/404/410/429)

Configuration:
- IZANAMI_CLI_AUTH_STORAGE: "in-memory" (default) or "postgresql"

Frontend:
- Enhanced login page to detect CLI auth and show completion message

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The OIDC browser-flow tests were failing because callOpenIdCallback2 was
not sending the state parameter in the callback request. The LoginController
requires both code and state for CSRF validation in browser flow.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant