Skip to content
Ashish.
All posts
Diagram illustrating the Keycloak SPI registry and provider lifecycle.
6 min readDevelopmentJava Developers, Platform EngineersFeatured#keycloak#spi#java#authentication#custom-provider#extension#backend

The Keycloak Service Provider Interface, Explained

A technical examination of the Keycloak Service Provider Interface (SPI), covering custom providers, provider factories, and extension strategies for Java developers.

By Ashish KumarPart 4 of Keycloak Themes and SPIs

This is Part 4 of the Keycloak Themes and SPIs series.

Keycloak is not a monolithic black box; it is a collection of modular services that communicate through a strict contract. For Java developers and platform engineers, the entry point into this ecosystem is the Service Provider Interface (SPI). Unlike simple plugin systems that rely on reflection or dynamic class loading without structure, Keycloak’s SPI is built on the JBoss Modules/WildFly SPI model. It uses a registry pattern where core services request implementations from a centralized factory map, allowing you to swap out authentication logic, user storage backends, or theme resolvers at runtime.

The core mechanism relies on two concepts: Providers (the stateful or stateless logic you write) and ProviderFactories (the stateless creators that instantiate your providers and manage their configuration).

How the SPI Registry Works

When Keycloak starts, it scans the classpath and the providers/ directory for implementations of specific interfaces. It does not guess which classes to load; it uses Java’s ServiceLoader mechanism, reading META-INF/services files, to locate classes that implement the required SPI interfaces.

This process is driven by the ProviderFactory interface. Every SPI extension must implement a factory that extends ProviderFactory<YourProviderInterface>. The factory has two critical responsibilities:

  1. Discovery: It defines the SPI context (e.g., UserStorage, Authentication).
  2. Instantiation: It creates instances of the provider when needed.

Keycloak injects dependencies into your providers via the constructor. This ensures that your custom code remains decoupled from the global application context. You do not use new MyProvider(). Instead, the SPI container calls factory.create(context, config), passing in the realm context and any user-defined configuration.

For more details on the SPI architecture, refer to the Keycloak SPI Documentation.

Implementing a Custom User Storage Provider

To understand the mechanism, let’s build a custom UserStorageProvider. This provider will allow Keycloak to authenticate users against a hypothetical external REST API instead of a database.

First, you implement the UserStorageProvider interface. This interface, together with related mixins like UserLookupProvider and CredentialInputValidator, defines methods like getUserByUsername and isValid.

public class RestApiUserStorageProvider implements UserStorageProvider {
 
    private final RealmModel realm;
    private final String apiUrl;
 
    // Constructor injection: Keycloak injects RealmModel and config values here
    public RestApiUserStorageProvider(RealmModel realm, Map<String, String> config) {
        this.realm = realm;
        this.apiUrl = config.get("api.url");
    }
 
    @Override
    public UserModel getUserByUsername(RealmModel realm, String username, boolean caseSensitive) {
        // Call external API using apiUrl
        // Return UserModel or null
        return null;
    }
 
    @Override
    public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
        // Validate password against external API
        return false;
    }
    
    // Other required methods...
}

Note that the provider is not thread-safe by default in older Keycloak versions, but modern implementations often rely on immutability or thread-local state. The RealmModel passed in the constructor is the bridge to Keycloak’s internal state, allowing you to look up roles, groups, and other users.

The Provider Factory: Bridging Configuration and Code

The provider alone is useless without a factory. The factory tells Keycloak that your provider exists, what it is called, and how to configure it.

You must implement UserStorageProviderFactory. This class is stateless and typically lives in a separate package or module.

public class RestApiUserStorageProviderFactory implements UserStorageProviderFactory<UserStorageProvider> {
 
    @Override
    public String getId() {
        return "rest-api-provider";
    }
 
    @Override
    public UserStorageProvider create(RealmModel realm, Map<String, String> config) {
        // Validation step: Ensure required config keys exist
        if (config.get("api.url") == null) {
            throw new ProviderConfigException("api.url is required");
        }
        return new RestApiUserStorageProvider(realm, config);
    }
 
    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        // Define the form fields in the Admin Console
        return Arrays.asList(
            new ProviderConfigProperty("api.url", "API URL", 
                "The base URL of the external user service", 
                ProviderConfigProperty.STRING_TYPE, null)
        );
    }
    
    @Override
    public void init(Scope scope) { /* Optional initialization */ }
    @Override
    public void postInit(KeycloakSessionFactory factory) { /* Optional post-init */ }
    @Override
    public void close() { /* Cleanup */ }
}

The getConfigProperties() method is crucial for platform engineers. It generates the UI in the Keycloak Admin Console under the "User Federation" tab. When an admin enters http://api.example.com/users into the text field, Keycloak passes that string as the value for the key api.url into the config map, which is then injected into your provider’s constructor.

Deployment and Auto-Discovery

Keycloak does not require XML configuration for your SPI, but it does require you to register your provider factory in a META-INF/services file following the standard Java ServiceLoader convention.

  1. Package the Code: Compile your provider and factory into a JAR file.
  2. Deploy: Drop the JAR into Keycloak’s providers/ directory (or configure it via the --spi-* CLI options in newer versions).

Upon startup, Keycloak’s provider manager scans the providers/ directory and classpath modules using the ServiceLoader mechanism, loads the factory class, and registers it in the internal map. When you navigate to the Admin Console and select "Rest API Provider" from the User Federation list, Keycloak looks up the factory by ID (rest-api-provider), calls create(), and injects the provider into the authentication flow.

Conclusion

The SPI pattern enforces a clean separation between Keycloak’s core logic and your business logic. You do not need to fork Keycloak. You do not need to hack into the authentication filter chain directly. You simply implement the contract, define the configuration, and let the registry handle the wiring.

For platform engineers, this means you can build custom identity backends, integrate with legacy LDAP systems, or implement complex MFA strategies while keeping Keycloak upgrades intact. The tradeoff is complexity: you must manage the JAR lifecycle and ensure your provider handles concurrency correctly. But the mechanism is stable, well-documented, and the standard way to extend Keycloak’s capabilities.

The Keycloak SPI provides a structured, dependency-injection-based path for extending core functionality without modifying the source code. By understanding the relationship between ProviderFactory and Provider, and mastering the deployment to the providers/ directory, developers can build reliable, maintainable integrations that survive platform upgrades.

Related posts