Skip to main content

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.

  1. Per-tenant IdP configuration. A tenant_idp_config model (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.
  2. JIT provisioning → existing RBAC. On first federated login, map IdP group/role claims to the app's ROLE_PERMISSIONS roles (officer/mlro/tenant_admin/super_admin) via a declarative per-tenant mapping. Reuse the existing user store; dedupe by verified email + tenant.
  3. Home-realm discovery by email domain → tenant → IdP, so users land on the right IdP without choosing.
  4. 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)

#IncrementShippable on its own?Effort
1tenant_idp_config model + migration + admin-API Keycloak IdP provisioningyes~1 wk
2OIDC federation (Entra/Okta/Google) + home-realm discovery + JIT→RBAC mapping + testsyes~1.5 wk
3SAML 2.0 federation (generic) + attribute mapping + testsyes~1.5 wk
4Hardening: SLO, IdP-initiated, error UX, audit events for federated loginyes~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.