Writing Keycloak extensions: Key concepts and anti-patterns
Tags: keycloak, java, programming, tutorial
Published: 2022-11-11 12:05:58

Keycloak comes preconfigured with a number of industry-standard providers, themes, and connectors. These built-in features are fairly robust, but they may not always be sufficient. To implement custom features, Keycloak allows you to use a number of service provider interfaces ([SPI](https://en.wikipedia.org/wiki/Service_provider_interface)). By extending them, you can add a new social identity provider or make a unique form action to tell the difference between human and automated access.

## Key concepts

There are two fundamental components that make up a custom Keycloak service provider: a `ProviderFactory` and the actual `Provider` itself. 

### ProviderFactory

As implied by its name, the `ProviderFactory` is the component that is in charge of actually creating the `Provider`. The factory will be discovered once the Keycloak server is launched, and only **one** instance of it will exist for the duration of the server's operation. Its interface, limited to the essential bits, is as follows: 

```java
public interface ProviderFactory<T extends Provider> {

    void init(Config.Scope config);

    void postInit(KeycloakSessionFactory factory);

    void close();

    T create(KeycloakSession session);

    // not mentioned: getId, order, getConfigMetadata

}
```

Now, let's examine each method in greater depth.

#### void init(Config.Scope config)

During the initialization of the factory, `init` will be called once and only once. The configuration will be obtained from the `keycloak_server.json` file. You can use this method to initialize and create objects that will be utilized in your implementation of the provider. To illustrate, HTTP clients and database clients are two examples of objects that should almost never be created more than once.

#### void postInit(KeycloakSessionFactory factory)

After all provider factories have been initialized, the function `postInit` will be invoked. You can now access any additional `ProviderFactories` and SPIs that you require. Further more, if your business logic calls for it, you might also initialize recurring tasks. 

#### void close()

The `close` method is called when the server shuts down. Resources, such as HTTP clients, created in the `init` or `postInit` methods should be released here.

#### T create(KeycloakSession session)

This is the method that creates the actual provider instance. Keycloak is calling this method on each **request** so the provider object itself should be light-weight.

### Provider

Each time a request is made, Keycloak invokes the `create` function on the `ProviderFactory` to generate a new provider object. In the provider, you'll implement the business logic of the functionality you want to add to your system. The only truly essential part of the interface is the `close` method, which frees up any resources that were allocated for the request but are no longer in use. Other methods will depend on the actual provider interface you want to implement.

### Provider discorvery

To tell Keycloak where to find the `ProviderFactory`, you have to set up a file in `META-INF/services` named after the specific service provider implementation and the fully qualified name of your service `ProviderFactory` as the file content.

## Anti-Patterns

Having covered the fundamentals of what a custom provider should look like, we can now examine bad practices that may arise while developing a Keycloak extension.

### AP#1 - Implementing ProviderFactory and Provider in the same class

Take a look at the following example, in which a `ProviderFactory` and a `Provider` are implemented in the same class.

```java
class AntiPatternProvider implements ProviderFactory<AntiPatternProvider>, Provider {

    private HTTPClient httpClient;

    @Override
    public AntiPatternProvider create(KeycloakSession session) {
        httpClient.post("https://example.com");

        return this;
    }

    @Override
    public void init() {
        httpClient = new HTTPClient();
    }

    @Override
    public void close() {
        httpClient.close();
    }

    // other implementation specific functions...
}
```

Yes, it would appear odd, but in theory, this is a legitimate provider. Can you identify any potential issues?

The server will start up without any problems, but as soon as the provider receives a second request, an error message stating that the HTTP client has already been closed will appear. Keycloak has no means of knowing that we only want to close the HTTP client when the server is shutting down. Instead, each request will result in the client closing. This is because we are implementing the `close` method of both the `ProviderFactory` and the `Provider`, which, as we have seen, have different responsibilities.

We can avoid the issue by implementing the `Provider` and the `ProviderFactory` in separate classes.

```java
class BestPracticeProvider implements Provider {
    
    private final HTTPClient httpClient;

    public BestPracticeProvider(HTTPClient httpClient) {
        this.httpClient = httpClient;
    }

    @Override
    public void close() {
        // This method will be called on each request. 
        // Do not close the HTTP client here!
    }
}

class BestPracticeProviderFactory implements ProviderFactory<BestPracticeProvider> {
    
    private HTTPClient httpClient;

    @Override
    public BestPracticeProvider create(KeycloakSession session) {
        return new BestPracticeProvider(httpClient);
    }

    @Override
    public void init() {
        httpClient = new HTTPClient();
    }

    @Override
    public void close() {
        // The server is shutting down. 
        // We can safely close the HTTP client.
        httpClient.close();
    }

    // other implementation specific functions...
}
```


### AP#2 - Creating heavy-weight objects during requests

In the example below, we will look at another anti-pattern that may arise while creating a provider.

```java
class AntiPatternProvider implements Provider {

    private final HTTPClient httpClient;

    public AntiPatternProvider() {
        httpClient = new HTTPClient();
    }

    @Override
    public void close() {
        httpClient.close();
    }
}

class AntiPatternProviderFactory implements ProviderFactory<AntiPatternProvider> {

    @Override
    public AntiPatternProvider create(KeycloakSession session) {
        return new AntiPatternProvider();
    }

    // other implementation specific functions...
}
```

In this provider implementation, the HTTP client is created and closed during each request. The issue is that the client is a heavy object. Creating it for every request is expensive. Similarly to how we fixed [AP#1](AP#1 - Implementing ProviderFactory and Provider in the same class), we need to have the `ProviderFactory` handle both the creation and closing of the HTTP client.


## Conclusion

Keycloak offers a lot of possibilities to extend its built-in functionality. Anyone working on an extension should familiarize themselves with the [Keycloak SPI](https://www.keycloak.org/docs/latest/server_development/#_providers) first. In my opinion, Keycloak's documentation and examples aren't great, especially for developers who are just getting started.

So, here are a few general recommendations when you are starting out:

* Treat each provider as a self-contained application.
* Keep an eye out for resources that have to be released once the server is shut down or the request is finished. Otherwise, you may unintentionally create memory leaks.
* Avoid having multiple providers in the same library whenever possible. Instead, try to split them up per domain. This will make it easier to test, maintain, and deploy them.
* Keep your providers up to date with the latest Keycloak version.
* Do not package external libraries with [shadowJar](https://github.com/johnrengelman/shadow) (only applies to the  Quarkus distribution). Instead, add the libraries to the `providers` directory.

## Thank you for reading

Do you have any questions? Was this article helpful? Let me know in the comments below.