Summary
The identity/authentication layer is currently coupled to Keycloak in several places beyond what the existing IdentityProvider interface abstracts. This makes it difficult to deploy Minder with alternative OIDC providers (Okta, Auth0, Zitadel, Dex, etc.). The codebase already has a clean IdentityProvider interface and multi-provider IdentityClient registry, but several areas bypass these abstractions and call Keycloak's Admin API or hardcode Keycloak URL patterns directly.
There are existing TODO comments in the code that acknowledge this intent:
cmd/server/app/serve.go:83-86 — notes that IssuerUrl should use .well-known/openid-configuration to remove Keycloak-specific paths
cmd/cli/app/auth/offline_token/offline_use.go:70 — notes that proper OIDC discovery should be used
internal/util/cli/credentials.go:195 — suggests using rp.NewRelyingPartyOIDC from zitadel instead of constructing URLs manually
Areas of Keycloak coupling
1. Hardcoded realm-based URL construction
Endpoints are constructed using Keycloak's /realms/{realm}/protocol/openid-connect/* path convention instead of using standard OIDC discovery (/.well-known/openid-configuration).
Affected files:
pkg/config/server/identity.go — GetRealmPath(), Realm field
cmd/server/app/serve.go — JWKS URL construction
internal/util/cli/credentials.go — token and refresh endpoints
cmd/cli/app/auth/auth_logout.go — revocation endpoint
cmd/cli/app/auth/offline_token/offline_use.go — token endpoint
pkg/config/client/identity.go — Realm field
2. Admin API calls outside the IdentityProvider interface
User deletion and event polling call Keycloak's Admin REST API directly via IdentityConfig.AdminDo(), bypassing the IdentityProvider abstraction.
Affected files:
internal/controlplane/identity_events.go — polls /admin/realms/{realm}/events and /admin/realms/{realm}/admin-events
internal/controlplane/handlers_user.go — calls Admin API to delete users from Keycloak
3. Auto-generated Keycloak Admin API client
internal/auth/keycloak/client/ contains a ~200KB auto-generated OpenAPI client for Keycloak's Admin API (v25.0.6). This is used for user lookups in Resolve().
4. Keycloak-specific event sync model
identity_events.go uses a poll-based cron job (every 5 minutes) to fetch DELETE_ACCOUNT user events and admin DELETE events from Keycloak. Other providers use different models (webhooks, System Log API, etc.), and generic OIDC has no standard for user lifecycle events.
Proposed approach
Phase 1: Adopt OIDC discovery everywhere
Replace all hardcoded /realms/{realm}/protocol/openid-connect/* URL construction with standard OIDC discovery. Fetch /.well-known/openid-configuration at startup and use the token_endpoint, jwks_uri, authorization_endpoint, revocation_endpoint, and device_authorization_endpoint from the discovery document.
This removes the Realm config field and GetRealmPath() helper from both server and CLI config.
Phase 2: Extend the identity provider interface for user management
Move user deletion and event syncing behind the IdentityProvider interface (or a new IdentityManager interface):
type IdentityManager interface {
IdentityProvider
// DeleteUser removes a user from the identity provider
DeleteUser(ctx context.Context, userID string) error
// SyncDeletionEvents returns users deleted since the given timestamp
SyncDeletionEvents(ctx context.Context, since time.Time) ([]DeletionEvent, error)
}
The Keycloak implementation would keep its current polling logic. Other providers could implement webhook-based or no-op variants.
Phase 3: Isolate the Keycloak client as an implementation detail
Keep internal/auth/keycloak/ as one concrete implementation. The auto-generated client becomes internal to that package. Alternative providers would use their own SDKs or lightweight HTTP clients behind the same interface.
Phase 4: Support pluggable event sync strategies
Refactor identity_events.go to support multiple sync models:
- Poll-based (current Keycloak approach)
- Webhook-based (for Okta, Auth0, etc.)
- No-op (for providers without event support, relying on Minder-side deletion only)
Benefits
- Deployers can bring their own OIDC provider instead of being forced to run Keycloak
- Reduces operational burden for deployments that already have an identity provider
- Makes the project more attractive for enterprise adoption where Okta/Azure AD/etc. are standard
- Completes the abstraction pattern the codebase already started with
IdentityProvider/IdentityClient
Summary
The identity/authentication layer is currently coupled to Keycloak in several places beyond what the existing
IdentityProviderinterface abstracts. This makes it difficult to deploy Minder with alternative OIDC providers (Okta, Auth0, Zitadel, Dex, etc.). The codebase already has a cleanIdentityProviderinterface and multi-providerIdentityClientregistry, but several areas bypass these abstractions and call Keycloak's Admin API or hardcode Keycloak URL patterns directly.There are existing TODO comments in the code that acknowledge this intent:
cmd/server/app/serve.go:83-86— notes thatIssuerUrlshould use.well-known/openid-configurationto remove Keycloak-specific pathscmd/cli/app/auth/offline_token/offline_use.go:70— notes that proper OIDC discovery should be usedinternal/util/cli/credentials.go:195— suggests usingrp.NewRelyingPartyOIDCfrom zitadel instead of constructing URLs manuallyAreas of Keycloak coupling
1. Hardcoded realm-based URL construction
Endpoints are constructed using Keycloak's
/realms/{realm}/protocol/openid-connect/*path convention instead of using standard OIDC discovery (/.well-known/openid-configuration).Affected files:
pkg/config/server/identity.go—GetRealmPath(),Realmfieldcmd/server/app/serve.go— JWKS URL constructioninternal/util/cli/credentials.go— token and refresh endpointscmd/cli/app/auth/auth_logout.go— revocation endpointcmd/cli/app/auth/offline_token/offline_use.go— token endpointpkg/config/client/identity.go—Realmfield2. Admin API calls outside the IdentityProvider interface
User deletion and event polling call Keycloak's Admin REST API directly via
IdentityConfig.AdminDo(), bypassing theIdentityProviderabstraction.Affected files:
internal/controlplane/identity_events.go— polls/admin/realms/{realm}/eventsand/admin/realms/{realm}/admin-eventsinternal/controlplane/handlers_user.go— calls Admin API to delete users from Keycloak3. Auto-generated Keycloak Admin API client
internal/auth/keycloak/client/contains a ~200KB auto-generated OpenAPI client for Keycloak's Admin API (v25.0.6). This is used for user lookups inResolve().4. Keycloak-specific event sync model
identity_events.gouses a poll-based cron job (every 5 minutes) to fetchDELETE_ACCOUNTuser events and admin DELETE events from Keycloak. Other providers use different models (webhooks, System Log API, etc.), and generic OIDC has no standard for user lifecycle events.Proposed approach
Phase 1: Adopt OIDC discovery everywhere
Replace all hardcoded
/realms/{realm}/protocol/openid-connect/*URL construction with standard OIDC discovery. Fetch/.well-known/openid-configurationat startup and use thetoken_endpoint,jwks_uri,authorization_endpoint,revocation_endpoint, anddevice_authorization_endpointfrom the discovery document.This removes the
Realmconfig field andGetRealmPath()helper from both server and CLI config.Phase 2: Extend the identity provider interface for user management
Move user deletion and event syncing behind the
IdentityProviderinterface (or a newIdentityManagerinterface):The Keycloak implementation would keep its current polling logic. Other providers could implement webhook-based or no-op variants.
Phase 3: Isolate the Keycloak client as an implementation detail
Keep
internal/auth/keycloak/as one concrete implementation. The auto-generated client becomes internal to that package. Alternative providers would use their own SDKs or lightweight HTTP clients behind the same interface.Phase 4: Support pluggable event sync strategies
Refactor
identity_events.goto support multiple sync models:Benefits
IdentityProvider/IdentityClient