Custom auth (OIDC)

Point Mizpah at your identity provider so only allowed users can use a shared hub.

By default Mizpah is a local, unauthenticated hub on 127.0.0.1. When you run it on a server and want specific people to open the UI, enable opt-in OIDC and point it at your IdP (Okta, Microsoft Entra ID, Keycloak, Google Workspace, Auth0, or any standards-compliant issuer).

Local loopback use stays unchanged until you set auth.enabled = true.

What you get

SurfaceWhen auth is on
Web UI, query APIs, /wsSession cookie after OIDC login, or Authorization: Bearer <apiToken>
Machine ingest (POST /api/ingest*)Loopback exempt, or Authorization: Bearer <ingestToken>
Hub discoveryPublic GET /api/health (no login)
Self-update applyStill loopback-only — OIDC does not grant it

Authorization v1 is an email / domain allowlist only (no roles or per-project ACLs).

Prerequisites

  1. A confidential OIDC application at your IdP (client id + client secret). Public / secretless PKCE-only apps are not supported in v1.
  2. HTTPS in front of the hub (reverse proxy). The browser redirect URI must be HTTPS in production.
  3. Mizpah config directory writable by the hub process (session signing secret is stored there unless you set MIZPAH_SESSION_SECRET).

Config file: config.toml under the Mizpah config dir (MIZPAH_CONFIG_DIR, or the platform path from directories — typically ~/Library/Application Support/dev.ethira.mizpah/ on macOS, ~/.config/mizpah/ on Linux).

1. Register the app at your IdP

Create a confidential / web application and set:

SettingValue
Redirect / callback URIhttps://<your-host>/api/auth/callback
Grant typeAuthorization code
Scopesat least openid, email (and usually profile)
Client authenticationClient secret (confidential)

Copy the issuer URL, client id, and client secret. Issuer examples:

ProviderTypical issuerUrl
Keycloakhttps://idp.example.com/realms/<realm>
Oktahttps://<org>.okta.com (or the custom auth server issuer)
Microsoft Entra IDhttps://login.microsoftonline.com/<tenant-id>/v2.0
Googlehttps://accounts.google.com
Auth0https://<tenant>.auth0.com/

Mizpah fetches {issuerUrl}/.well-known/openid-configuration at hub start and fails fast if discovery fails.

2. Configure Mizpah

Add an [auth] section to config.toml:

[auth]
enabled = true
issuerUrl = "https://login.example.com/realms/prod"
clientId = "mizpah"
# Prefer env: MIZPAH_OIDC_CLIENT_SECRET — leave empty here
clientSecret = ""
redirectUri = "https://logs.example.com/api/auth/callback"
scopes = ["openid", "profile", "email"]
allowedEmails = ["alice@example.com"]
allowedDomains = ["example.com"]
ingestToken = ""   # or set MIZPAH_INGEST_TOKEN
apiToken = ""      # or set MIZPAH_API_TOKEN
sessionTtlHours = 12

Allowlists

Secrets (prefer environment)

VariablePurpose
MIZPAH_OIDC_CLIENT_SECRETOIDC client secret (required when auth is enabled)
MIZPAH_INGEST_TOKENBearer for remote ingest / forwarders
MIZPAH_API_TOKENBearer for MCP and non-browser API clients
MIZPAH_SESSION_SECRETOptional; otherwise Mizpah generates session.secret under the config dir

Do not commit real secrets into config.toml if the file is shared or checked in.

3. Put TLS in front of the hub

Example pattern:

  1. Bind Mizpah on loopback: --host 127.0.0.1 --port 3149
  2. Reverse proxy (Caddy, nginx, Traefik, …) terminates HTTPS and proxies to 127.0.0.1:3149
  3. Forward X-Forwarded-Proto: https so the session cookie is marked Secure
  4. Set redirectUri to the public HTTPS callback URL

If you must bind a non-loopback interface, pass --allow-remote. With auth enabled, Mizpah still warns that ingest needs a token or loopback; with auth disabled, the warning is stronger.

4. Start the hub and sign in

export MIZPAH_OIDC_CLIENT_SECRET='…'
export MIZPAH_INGEST_TOKEN='…'   # for remote pipes / Vector / Fluent Bit
export MIZPAH_API_TOKEN='…'      # for mzp mcp / scripts

mzp hub start --host 127.0.0.1 --port 3149
# or, if binding on a public interface behind your own controls:
# mzp hub start --host 0.0.0.0 --port 3149 --allow-remote

Open https://logs.example.com/. The SPA loads; the first API call that returns 401 redirects the browser to /api/auth/login, then to your IdP, then back to /api/auth/callback, which sets the mizpah_session cookie and redirects to /.

Useful endpoints:

PathRole
GET /api/auth/loginStart OIDC (PKCE + state)
GET /api/auth/callbackCode exchange, allowlist, set cookie
POST /api/auth/logoutClear cookie
GET /api/auth/meCurrent user (or 401)
GET /api/healthAlways public liveness

5. Wire machine clients

Ingest (pipes, attach, file forwarders, Vector)

On non-loopback ingest, send the ingest bearer:

export MIZPAH_INGEST_TOKEN='…'
my-app | mzp --service api --host logs.example.com --port 443
# or HTTP:
curl -sS -X POST "https://logs.example.com/api/ingest/batch" \
  -H "Authorization: Bearer $MIZPAH_INGEST_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"service":"vector","lines":["{\"msg\":\"hi\"}"]}'

Loopback ingest to a hub on 127.0.0.1 does not need the token.

MCP / API query

export MIZPAH_URL='https://logs.example.com'
export MIZPAH_API_TOKEN='…'
mzp mcp

Checklist

  1. IdP confidential app with redirect https://<host>/api/auth/callback
  2. [auth] enabled = true with issuerUrl, clientId, redirectUri
  3. MIZPAH_OIDC_CLIENT_SECRET set in the hub environment
  4. Allowlist emails/domains (or leave both empty only if every IdP user may access logs)
  5. TLS reverse proxy + X-Forwarded-Proto
  6. MIZPAH_INGEST_TOKEN for any remote ingest path
  7. MIZPAH_API_TOKEN for MCP / automation

Troubleshooting

SymptomLikely cause
Hub exits on start: auth config: …Missing issuer/client/secret/redirect, or OIDC discovery failed
Login works but “email … is not allowed”Allowlist mismatch; check email claim and domains
Cookie not sticking / immediate re-loginMissing HTTPS or X-Forwarded-Proto (cookie needs Secure)
Remote ingest 401Set MIZPAH_INGEST_TOKEN on the client to match hub ingestToken
MCP 401Set MIZPAH_API_TOKEN to match hub apiToken
Callback 404 / HTML shellProxy must forward /api/auth/* to the hub (not only /)