Authentication
What you'll accomplish: Set up API key authentication or OIDC/SSO for your Hoziron deployment.
Authentication modes
Hoziron supports three modes, configured via [auth].mode:
| Mode | Use case |
|---|---|
disabled (default) | Local dev, single-user deployments — loopback bind only |
local | API key-based auth with RBAC |
oidc | Enterprise SSO (Azure AD, Keycloak, Okta, Auth0) |
Auth is mandatory for any carrier/production install. The daemon refuses to boot with auth.mode = "disabled" (the default) while bound to a non-loopback address — no [auth] section at all is treated the same as an explicit disabled. Loopback (127.0.0.1, ::1, localhost) and unix:// sockets are the only addresses disabled is allowed on. To run without auth on a network-reachable bind anyway (not recommended outside isolated dev/test), set auth.allow_insecure_no_auth = true; every boot then logs a tracing::warn! for the duration the escape hatch is in effect.
Local API key authentication
Enable
[auth]
mode = "local"
[auth.rate_limit]
base_backoff_secs = 1
max_backoff_secs = 300
max_failed_attempts = 10
Create your first key
When no keys exist yet, the first POST /auth/keys request is allowed without authentication (bootstrap bypass):
hoziron-cli auth create-key --role admin --name "platform-admin"
The key is shown once — save it securely. Format: hzk_Ab3xYz...
Manage keys
hoziron-cli auth create-key --role operator --name "ci-bot" --expires-in 90d
hoziron-cli auth list-keys
hoziron-cli auth revoke-key <key-id>
hoziron-cli auth rotate-key <key-id>
How it works
Key security details:
- Keys are 32 bytes of cryptographic randomness, base64url-encoded with
hzk_prefix - Only argon2id hashes are stored — the raw key is never persisted
- Validation uses constant-time comparison across all keys (prevents timing attacks)
- The last admin key cannot be revoked (prevents lockout)
OIDC / SSO configuration
For enterprise SSO with Azure AD, Keycloak, Okta, or Auth0:
[auth]
mode = "oidc"
allow_local_service_keys = true # API keys alongside OIDC for CI/CD
[auth.oidc]
issuer = "https://login.microsoftonline.com/{tenant}/v2.0"
audience = "api://hoziron-platform"
jwks_uri = "" # Auto-discovered if blank
role_claim = "roles" # Supports dot-path: "realm_access.roles"
allowed_algorithms = ["RS256", "ES256"]
jwks_cache_ttl_secs = 3600
[auth.oidc.role_mapping]
"HozironAdmins" = "admin"
"PlatformOps" = "operator"
"Developers" = "developer"
"ReadOnly" = "viewer"
Role mapping
The role_claim field supports dot-path traversal for nested claims (Keycloak pattern):
role_claim = "realm_access.roles"
When multiple IdP roles match, highest-privilege wins (admin > operator > developer > viewer > service).
Hybrid mode
With allow_local_service_keys = true, OIDC falls back to local API key validation when JWT validation fails. This enables:
- CI/CD pipelines using API keys alongside human SSO
- Break-glass access for emergencies
CLI device-code login (human operators)
For a human operator to authenticate the CLI against an OIDC-protected server, register an oidc context and log in — this runs the OAuth 2.0 Device Authorization Grant (RFC 8628) against your IdP, not a local API key:
hoziron-cli context add production \
--url https://hoziron.internal.example.com \
--type api \
--auth oidc \
--issuer "https://login.microsoftonline.com/{tenant}/v2.0" \
--client-id <your-idp-client-id>
hoziron-cli context use production
hoziron-cli login
hoziron-cli login opens a device code + verification URL (auto-opens a browser when one is available; works over SSH/headless the same way — approve the code in any browser). On success the CLI caches the token (id_token, falling back to access_token) at ~/.hoziron/token.json with 0600 permissions on Unix, keyed per context, and transparently refreshes it (using the granted refresh_token, if the IdP returned one) on subsequent commands. The signature is verified server-side on every request by OidcValidator — the CLI only decodes the JWT payload locally (unverified) to display identity and check expiry.
hoziron-cli auth whoami # confirm identity + resolved role
hoziron-cli logout # clear the cached session for the active context
Brute force protection
Per-IP exponential backoff on failed attempts:
| Parameter | Default | Config key |
|---|---|---|
| Base backoff | 1 second | auth.rate_limit.base_backoff_secs |
| Max backoff | 300 seconds (5 min) | auth.rate_limit.max_backoff_secs |
| Max failures tracked | 10 | auth.rate_limit.max_failed_attempts |
Rate limit state is in-memory, per-IP, cleared on successful auth.
Endpoints always accessible
Regardless of auth configuration:
| Endpoint | Reason |
|---|---|
GET /health | Orchestrator probes must always work |
GET /metrics | Prometheus scraping |
POST /auth/keys (first key only) | Bootstrap — create first key |
Next steps
Related: