ADR 0003: Authentik for Authentication¶
Status: Accepted Date: 2026-04-29
Context¶
SCALR needs unified authentication across multiple apps. A user logs in once at the portal and is recognized by every other app they're entitled to use. This rules out per-app auth and points squarely at a central identity provider issuing OIDC tokens.
The realistic candidates were:
- Keycloak — battle-tested enterprise IdP from Red Hat. Massive feature set.
- Authentik — modern open-source IdP with a visual flow builder. Cleaner UX than Keycloak.
- Ory (Kratos + Hydra + Keto + Oathkeeper) — modular, API-first, four services to operate.
- Authelia — forward-auth proxy gate, not a full IdP.
- Build our own — JWT issuing, JWKS rotation, refresh, MFA, password reset.
Decision¶
Use Authentik, self-hosted in services/auth/.
Reasons specific to SCALR's situation:
- Stack fit. Authentik already wants Postgres + Redis, both of which we run anyway for the rest of the platform.
- Operational simplicity. Single Docker container for the server + one for the worker. Slots into the root
docker-compose.ymlcleanly. - Standards-compliant. Issues real OIDC/JWT tokens, so our SDKs (
auth-client-py,auth-client-ts) are written against the OIDC standard, not Authentik specifically. Provider lock-in is contained toservices/auth/configuration only. - Visual flow builder. When we eventually want MFA, social login, or custom signup flows, we configure them in the UI instead of writing code.
- Right-sized. Heavier than Authelia, far lighter than Keycloak. Fits a single-operator home lab without enterprise overhead.
Why not the others¶
- Keycloak. Production setup is genuinely difficult — SSL, proxies, hostname configuration, and documentation gaps. Massive feature set we don't need yet. Right answer if/when we hit enterprise federation needs, not before.
- Ory. Modular by design — running 3-4 services for what Authentik does in one. Too many moving parts for current scale.
- Authelia. Doesn't issue OIDC tokens. Wrong tool — apps can't get user identity from it cleanly.
- Build our own. JWT signing, JWKS rotation, refresh tokens, password reset, MFA — too much to get right ourselves, with no upside.
Consequences¶
Positive:
- Standard OIDC means the SDKs are simple wrappers around well-tested libraries (authlib in Python, oidc-client-ts in TypeScript).
- Authentik's "Application" + "Group" model maps cleanly to our entitlement needs.
- The portal queries Authentik for the user's entitled applications and intersects with discovered manifests — see 0005-app-manifest-schema.md.
Negative: - Authentik's data model (flows, stages, providers, applications) takes a few hours to understand the first time. - We're tied to running another stateful service. Mitigated by it sharing Postgres and Redis with everything else.
Migration path¶
If Authentik ever has to be replaced, only services/auth/ configuration changes. The SDKs and apps don't change because they speak generic OIDC.