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:

ModeUse case
disabled (default)Local dev, single-user deployments — loopback bind only
localAPI key-based auth with RBAC
oidcEnterprise 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:

ParameterDefaultConfig key
Base backoff1 secondauth.rate_limit.base_backoff_secs
Max backoff300 seconds (5 min)auth.rate_limit.max_backoff_secs
Max failures tracked10auth.rate_limit.max_failed_attempts

Rate limit state is in-memory, per-IP, cleared on successful auth.

Endpoints always accessible

Regardless of auth configuration:

EndpointReason
GET /healthOrchestrator probes must always work
GET /metricsPrometheus scraping
POST /auth/keys (first key only)Bootstrap — create first key

Next steps


Related: