Skip to content

Generalize identity provider layer to decouple from Keycloak #6217

@JAORMX

Description

@JAORMX

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.goGetRealmPath(), 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.goRealm 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions