
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.
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:
- Discovery: It defines the SPI context (e.g.,
UserStorage,Authentication). - 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.
- Package the Code: Compile your provider and factory into a JAR file.
- 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
Writing a Keycloak Custom Authenticator
A guide to building a Keycloak custom authenticator using the Authenticator SPI for Java developers and identity engineers.
Keycloak SPI Development: Building Custom Providers
Learn to build custom Keycloak providers using the Service Provider Interface for enhanced extensibility and plugin development.
Keycloak Custom Authentication Flows: Building Custom Login Experiences
An examination of Keycloak authentication flows, custom auth strategies, and SPI implementation for building tailored login experiences.