Skip to content

OIDC Configuration

Klangk supports OIDC authentication via one or more external Identity Providers (e.g., Keycloak). This is used for CAC card login and other SSO scenarios. Klangk is a standard OIDC relying party — the IdP handles all certificate/credential complexity.

Setup

  1. Create a YAML config file with your OIDC providers:
- id: cac
  display_name: CAC Login
  issuer: https://keycloak.example.com/realms/company
  client_id: klangk
  client_secret: "file:/run/secrets/cac-secret"
  scopes: openid email profile
  ca_cert: /etc/pki/tls/certs/company-ca-bundle.pem
  logout_redirect: true

- id: internal
  display_name: Internal SSO
  issuer: https://keycloak.example.com/realms/corp
  client_id: klangk
  client_secret: "file:/run/secrets/corp-secret"
  1. Set KLANGK_OIDC_CONFIG in .env:
KLANGK_OIDC_CONFIG=/path/to/oidc.yaml
  1. Optionally set KLANGK_AUTH_MODES to control which login methods are available:
  2. both (default when OIDC configured) — SSO buttons + email/password form
  3. oidc — SSO buttons only, email/password disabled
  4. password — email/password only (same as no OIDC config)

Provider Config Fields

Field Required Description
id Yes URL-safe slug, used in endpoint paths (/auth/oidc/{id}/login) and stored as provider on users
display_name Yes Button label on the login page (e.g., "CAC Login", "Google")
issuer Yes OIDC issuer URL. Discovery via {issuer}/.well-known/openid-configuration
client_id Yes OIDC client ID registered with the IdP
client_secret Yes OIDC client secret. Supports file: prefix for secret management
scopes No Space-separated scopes (default: openid email profile)
ca_cert No Path to a CA certificate PEM file for IdPs with custom/private CAs
token_validation_pem No Inline RSA/EC public key PEM for static token validation (skips JWKS discovery)
logout_redirect No If true, logout redirects to the IdP's end_session_endpoint (RP-Initiated Logout). Default: false (local-only logout)

How It Works

  • Web: Login page shows one button per provider. Clicking redirects to the IdP via Authorization Code flow with PKCE. After authentication, the IdP redirects back to Klangk which exchanges the code for tokens, validates the ID token, and issues a Klangk JWT.
  • CLI: klangkc login detects OIDC from the server config, opens a browser for authentication, and receives the token via a temporary localhost callback server.
  • Login hook: A single Python hook (KLANGK_OIDC_LOGIN_HOOK) handles both login validation and group mapping. See OIDC Login Hook below.
  • User provisioning: On first OIDC login, a user is created automatically (verified, no password). If a local user with the same email already exists, the OIDC identity is linked to it.
  • OIDC users cannot use forgot-password, change-password, or change-email.
  • Logout: By default, logout only kills the Klangk session. With logout_redirect: true, the user is also redirected to the IdP's logout endpoint to end the SSO session (requires full re-authentication on next login).

IdP Setup (Keycloak Example)

  1. Create a client in your Keycloak realm:
  2. Client ID: klangk
  3. Client authentication: On (confidential)
  4. Valid redirect URIs: https://your-klangk-host/auth/oidc/cac/callback (one per provider ID)
  5. Web origins: https://your-klangk-host
  6. Copy the client secret to a file or set it directly in the OIDC config
  7. For CAC: configure the X.509 client certificate authenticator in the Keycloak authentication flow

OIDC Login Hook

A single Python hook handles both login validation and group mapping on every OIDC login.

Configuration:

KLANGK_OIDC_LOGIN_HOOK=my_module.on_login

The value is a dotted Python path where the last component is the function name. If not set, all OIDC logins are accepted with no group sync.

Hook signature:

def on_login(provider, claims, email, tokens):
    """Validate the login and optionally return group names.

    Args:
        provider: OIDCProvider object (id, issuer, client_id, etc.)
        claims: decoded ID token payload (sub, email, custom claims)
        email: user's email
        tokens: raw token response (id_token, access_token, etc.)

    Returns:
        None — login allowed, no group sync
        set[str] — login allowed, sync memberships to these groups

    Raises:
        Any exception — login rejected (message shown to user)
    """
    # Example: reject unverified emails
    if claims.get("email_verified") is not True:
        raise ValueError("Email not verified by identity provider")

    # Example: map IdP roles to groups
    groups = set()
    roles = claims.get("realm_access", {}).get("roles", [])
    if "klangk-admin" in roles:
        groups.add("admin")
    return groups or None

Async hooks are also supported (async def).

Behavior:

  • Called after ID token validation, before user provisioning
  • Raise an exception — login rejected (HTTP 403, exception message shown)
  • Return None — login allowed, no group sync
  • Return a set[str] — login allowed, memberships synced to those groups
  • Groups returned by the hook are auto-created if they don't exist
  • Memberships are tracked with source='oidc_sync' — only these are added/removed
  • Manual group memberships (source='manual') are never touched

Security note: The hook is entirely responsible for login validation and group mapping. There is no default hook — the hook is written by the deploying organization, and it is their responsibility to validate IdP claims as appropriate. Without a hook, all OIDC logins are accepted (including those with unverified emails).

Built-in example hooks:

# Reject logins with unverified emails
KLANGK_OIDC_LOGIN_HOOK=klangk_backend.oidc.example_require_verified_email

# Map Keycloak klangk-admin role to the admin group
KLANGK_OIDC_LOGIN_HOOK=klangk_backend.oidc.example_admin_hook