ADR-0076 — SSO federation (per-tenant IdP brokering on Keycloak)
Status: Proposed (design only — NOT implemented) Date: 2026-06-28 Deciders: (pending) — requires human authorization to execute Effort: ~3–6 weeks (the most session-tractable enterprise-certification item)
This ADR is the methodology-mandated design before code for the first enterprise-certification feature. It is Proposed, not Accepted — nothing is built. It exists so the multi-week work can be authorized and sequenced rather than started ad-hoc. See
docs/enterprise-readiness.md§3.
Context
enterprise-readiness.md honestly scopes SSO/SCIM/ISO/residency/pen-test as the
remaining tail to "enterprise-certified". SSO is the usual enterprise-sales
unblocker and the most tractable because Keycloak is already in the stack and
the app already authenticates via Keycloak OIDC (get_current_user() validates
Keycloak JWT/JWKS). What's missing is federation: letting an enterprise tenant
sign in through their own IdP (Azure AD / Entra, Okta, Google Workspace,
generic SAML 2.0 / OIDC).
Decision (proposed)
Use Keycloak's native Identity Brokering rather than building federation in the app. Keycloak is the broker; customer IdPs are configured per realm/tenant. The FastAPI app keeps validating Keycloak-issued JWTs unchanged — federation is transparent to it.
- Per-tenant IdP configuration. A
tenant_idp_configmodel (issuer, protocol SAML|OIDC, client id/secret ref, attribute mappings) drives Keycloak IdP setup via the Keycloak Admin API. Secrets via the credential-request path / secret store — never stored in plaintext, never handled by the app in the clear. - JIT provisioning → existing RBAC. On first federated login, map IdP
group/role claims to the app's
ROLE_PERMISSIONSroles (officer/mlro/tenant_admin/super_admin) via a declarative per-tenant mapping. Reuse the existing user store; dedupe by verified email + tenant. - Home-realm discovery by email domain → tenant → IdP, so users land on the right IdP without choosing.
- Tenant isolation preserved. Federated identities are tenant-scoped; the existing RLS + RBAC layers are unchanged (SSO is authentication, not authorization — it composes with ADR-0074 RBAC and ADR-0023 RLS).
Non-goals (explicit, to bound the first increment)
- IdP-initiated SSO and SAML Single Logout (SLO) — phase 2.
- SCIM provisioning — separate ADR-0077 (deprovisioning lifecycle).
- Self-service IdP setup UI — phase 2 (phase 1 is admin-API-driven config).
Sequencing (program view)
| # | Increment | Shippable on its own? | Effort |
|---|---|---|---|
| 1 | tenant_idp_config model + migration + admin-API Keycloak IdP provisioning | yes | ~1 wk |
| 2 | OIDC federation (Entra/Okta/Google) + home-realm discovery + JIT→RBAC mapping + tests | yes | ~1.5 wk |
| 3 | SAML 2.0 federation (generic) + attribute mapping + tests | yes | ~1.5 wk |
| 4 | Hardening: SLO, IdP-initiated, error UX, audit events for federated login | yes | ~1 wk |
Each increment is independently shippable and testable (testcontainer Keycloak + a mock IdP) — so it slots into the normal PR-per-increment flow, not a big bang.
Consequences
- Closes the single biggest enterprise-sales gap with mostly configuration of an existing component, not new auth code — lowest-risk path.
- Federated login becomes an auditable event (feeds ADR-0064 audit trail / EU AI Act Art. 12).
- Does not by itself achieve "enterprise-certified" — SCIM (ADR-0077, TBD), data residency, and the ISO program remain. This ADR is increment 1 of that program, not its completion.
References
ADR-0074 (RBAC), ADR-0023 (RLS), ADR-0064 (audit trail), ADR-0072 (AI Act
conformity — names SSO/SCIM as known gaps), docs/enterprise-readiness.md.