Skip to content

ADR-004: Keycloak as Identity Provider over Azure AD B2C

Status

Accepted

Date

2023-Q3


Context

Microtec ERP requires an Identity Provider (IdP) for:

  • Single Sign-On (SSO) across 10+ Angular apps and 3 mobile apps
  • Multi-tenant user management (each tenant has its own users)
  • Custom authentication flows (company/branch selection post-login)
  • On-premise deployment capability (some enterprise customers prefer on-prem)
  • Role-based and attribute-based access control

Candidates evaluated:

IdPTypeKey Properties
Azure AD B2CAzure PaaSPer-MAU pricing, Microsoft-managed, limited extensibility
KeycloakOpen-sourceSelf-hosted, Java SPI extensibility, zero per-MAU cost
Auth0SaaSDeveloper-friendly, per-MAU pricing, limited on-prem
IdentityServer.NET libraryFull .NET control, requires building admin UI
Azure AD (Entra ID)Azure PaaSPrimarily for enterprise M365 users, complex for B2C

Decision

Use Keycloak 24.x as the Identity Provider, hosted on Azure Container Apps.

Reasons for Keycloak over Azure AD B2C

CriterionAzure AD B2CKeycloak (chosen)
Cost modelPer-MAU ($0.0016–$0.0325/user/month)Self-hosted, no per-user cost
Custom SPILimited (custom policies, complex)Full Java SPI — any logic possible
Multi-realmSingle tenant with complex policiesNative multi-realm support
On-premiseNot possible (Azure-only)Supports on-premise deployment
Admin APILimitedFull Admin REST API
CommunityMicrosoftLarge open-source community
ExtensibilityHTML/JavaScript customizationJava extensions, custom authenticators
Vendor lock-inHigh (Azure-only)Low (runs anywhere)

Why Custom SPIs Were Required

The ERP's authentication requirements could not be met by stock Keycloak:

  1. Company/Branch Selection: After authentication, users must select which company and branch they're operating in. This state must be embedded in the JWT. Stock Keycloak has no concept of "company" or "branch."

  2. ERP Permission Mapping: JWT claims must include ERP module permissions (erp_policies). These are stored in the ERP database, not in Keycloak. A custom SPI is required to call the ERP API during token issuance.

  3. Multi-Account Switcher: Users belonging to multiple companies must switch company/branch without re-authenticating. This requires a custom session management SPI.

  4. Tenant Seeding: When a new tenant is provisioned, their Keycloak realm must be created programmatically with the correct configuration. Azure AD B2C has no equivalent automated provisioning API.

Custom SPIs Developed

SPILanguagePurpose
company-branch-mapperJavaMaps company/branch to JWT claims
erp-policy-mapperJavaMaps ERP permissions to JWT claims
multi-account-switcherJavaCompany switch without re-login
erp-tenant-mapperJavaMaps tenant/subdomain to JWT claims
seeding-authenticatorJavaAutomated realm/user provisioning

All SPIs are in KeycloakProviders/ and deployed as a custom Keycloak Docker image.

Realm Architecture

Two realms to separate ERP users from admin users:

RealmUsersApplications
microtecTenant employeesAll ERP apps + mobile
businessownerTenant admins, resellersBO portal

Consequences

Positive

  • Zero per-MAU cost: No incremental cost as customer base grows
  • Full SPI extensibility: Any authentication/authorization logic implementable in Java
  • Multi-realm isolation: ERP users and BO admin users are completely isolated
  • On-premise capability: Enterprise customers can request on-prem deployment
  • Open standard: OIDC/OAuth 2.0 — frontend code is not Keycloak-specific
  • Rich admin API: Full programmatic control for tenant provisioning automation
  • Backchannel logout: Keycloak-native session revocation across all clients

Negative

  • Self-managed: We own updates, security patches, and high availability
  • Java SPI complexity: Custom SPIs require Java knowledge; .NET team must maintain Java code
  • Higher operational overhead than B2C: ACA deployment, PostgreSQL/SQL for Keycloak state, monitoring
  • SPI HTTP dependency: erp-policy-mapper calls ERP API synchronously during login — if the ERP service is down, logins fail
  • Version upgrades: Major Keycloak versions can break SPIs; upgrades require SPI re-testing

Neutral

  • Keycloak is deployed on ACA with Azure SQL as its storage backend
  • The custom Docker image (keycloak-with-spis) is built by the KeycloakProviders/ pipeline
  • Flow priorities set to: MAC=10, Cookie=20, IdP=30 (required for correct auth flow ordering)

Deployment Notes

Keycloak on ACA

Container:  keycloak-with-spis:latest
Image:      Built from KeycloakProviders/Dockerfile
Base:       quay.io/keycloak/keycloak:24.x
SPIs:       Compiled JARs copied to /opt/keycloak/providers/
Database:   Azure SQL (JDBC driver included)
Memory:     1.0 vCPU, 2Gi RAM minimum

Known Configuration

KC_DB=mssql
KC_DB_URL=jdbc:sqlserver://<host>;databaseName=Microtec.SSO
KC_DB_USERNAME=sa
KC_HOSTNAME=auth.<env-domain>
KC_PROXY_HEADERS=xforwarded
KC_HOSTNAME_STRICT=false
KC_FEATURES=persistent-user-sessions,token-exchange,admin-fine-grained-authz:v1
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=<from KV>

  • ADR-001: Microservices (Keycloak serves all services via standard OIDC)
  • ADR-002: Azure Container Apps (Keycloak hosted on ACA)
  • ADR-008: Dual-Token Design (Keycloak JWT is one of the two tokens)

Internal Documentation — Microtec Platform Team