ADR-002: Authentication Stack
Status: Accepted
ADR-002: Authentication Stack
Section titled “ADR-002: Authentication Stack”Context
Section titled “Context”arpi needs a user authentication mechanism for its CLI, control plane API, and web UI. Requirements include device code flow for CLI, standard OIDC for API auth, service accounts for CI/CD, and self-hosted operation.
Decision
Section titled “Decision”Self-hosted Zitadel (OIDC identity provider) for all authentication.
- Single Go binary, simpler ops than Ory (Kratos + Hydra)
- Native multi-org support maps to arpi’s org-scoped roles
- Built-in service accounts for machine identity (agents, CI/CD)
- Full OIDC provider with device code flow (RFC 8628) and PKCE
Implementation
Section titled “Implementation”- CLI: PKCE localhost callback with device code fallback (auto-detect)
- Server: Standard OIDC JWT validation via
coreos/go-oidcwithOIDC_ISSUER_URL/OIDC_CLIENT_ID - Custom claims: Zitadel Actions map roles to
arpi_rolesJWT claim - Org ID: Zitadel org ID used directly as
org_idclaim - CI/CD:
arpi login --tokenaccepts pre-exchanged Zitadel access token - Dev: Zitadel in docker-compose reusing arpi-postgres (port 8084)
- Staging/prod: Helm deployment
Consequences
Section titled “Consequences”- Tokens stored locally in
~/.config/arpi/auth.json(JSON, 0600 permissions) - Server validates JWTs via standard OIDC discovery
- AuthTransport handles Bearer injection and proactive refresh on expiry
- Generic
OIDC_*env vars — provider-agnostic configuration
Decision History
Section titled “Decision History”- 2026-03-25: Original decision — self-hosted Zitadel
- 2026-03-30: Override to WorkOS AuthKit (velocity, zero ops)
- 2026-03-31: Back to self-hosted Zitadel (WorkOS not friendly to local/self-hosted)