How to create a KeyCloak Provider

Keycloak offers a range of Service Provider Interfaces (SPIs) that enable specific functionalities within the server. By following this guide, you’ll understand how to code and install a custom Provider that you can add to your Keycloak installation.

Keycloak Providers in a nutshell

In Keycloak, a provider is an extension that adds specific functionality to the core system. These providers can handle various tasks, such as authentication, user federation, event handling, and more. They offer a way to customize Keycloak’s behavior and integrate it with different systems.Here’s a breakdown of Keycloak Providers:

  • Event Listeners: These providers listen for events happening within Keycloak, such as user login, logout, or creation. They can then perform actions based on these events, like sending notifications or logging data.
  • Authentication Providers: These providers handle user authentication against external identity sources like LDAP servers, social identity providers (e.g., Google, Facebook), or custom authentication mechanisms.
  • User Federation Providers: These providers allow you to synchronize user data between Keycloak and other user directories or databases.
  • SPI (Service Provider Interface) Implementations: Keycloak offers various Service Provider Interfaces (SPIs) for extending its functionalities in specific areas. These SPIs have corresponding provider implementations that plug into the core system.

Coding a simple Event Listener Provider

The simplest form of Provider is the Event Listener. In order to create your Event Listener Provider, you need to implement the following interfaces:

  • EventListenerProvider Interface: This interface contains the callback methods when the event is triggered.
  • EventListenerFactory: This factory allows the creation of instances of the EventListenerProvider.

An EventListener Provider example

This interface is used to handle events occurring within Keycloak. It has two methods:

  1. onEvent(Event event): This method is invoked whenever an event occurs in Keycloak. You can perform custom actions based on the event.
  2. onEvent(AdminEvent event, boolean includeRepresentation): Similar to onEvent(Event event), but specifically for administrative events.

Here is a sample implementation from Keycloak quickstarts:

public class SysoutEventListenerProvider implements EventListenerProvider {

    private Set<EventType> excludedEvents;
    private Set<OperationType> excludedAdminOperations;

    public SysoutEventListenerProvider(Set<EventType> excludedEvents, Set<OperationType> excludedAdminOpearations) {
        this.excludedEvents = excludedEvents;
        this.excludedAdminOperations = excludedAdminOpearations;
    }

    @Override
    public void onEvent(Event event) {
        // Ignore excluded events
        if (excludedEvents != null && excludedEvents.contains(event.getType())) {
            return;
        } else {
            System.out.println("EVENT: " + toString(event));
        }
    }

    @Override
    public void onEvent(AdminEvent event, boolean includeRepresentation) {
        // Ignore excluded operations
        if (excludedAdminOperations != null && excludedAdminOperations.contains(event.getOperationType())) {
            return;
        } else {
            System.out.println("EVENT: " + toString(event));
        }
    }

    private String toString(Event event) {
        StringBuilder sb = new StringBuilder();

        sb.append("type=");
        sb.append(event.getType());
        sb.append(", realmId=");
        sb.append(event.getRealmId());
        sb.append(", clientId=");
        sb.append(event.getClientId());
        sb.append(", userId=");
        sb.append(event.getUserId());
        sb.append(", ipAddress=");
        sb.append(event.getIpAddress());

        if (event.getError() != null) {
            sb.append(", error=");
            sb.append(event.getError());
        }

        if (event.getDetails() != null) {
            for (Map.Entry<String, String> e : event.getDetails().entrySet()) {
                sb.append(", ");
                sb.append(e.getKey());
                if (e.getValue() == null || e.getValue().indexOf(' ') == -1) {
                    sb.append("=");
                    sb.append(e.getValue());
                } else {
                    sb.append("='");
                    sb.append(e.getValue());
                    sb.append("'");
                }
            }
        }

        return sb.toString();
    }
    
    private String toString(AdminEvent adminEvent) {
        StringBuilder sb = new StringBuilder();

        sb.append("operationType=");
        sb.append(adminEvent.getOperationType());
        sb.append(", realmId=");
        sb.append(adminEvent.getAuthDetails().getRealmId());
        sb.append(", clientId=");
        sb.append(adminEvent.getAuthDetails().getClientId());
        sb.append(", userId=");
        sb.append(adminEvent.getAuthDetails().getUserId());
        sb.append(", ipAddress=");
        sb.append(adminEvent.getAuthDetails().getIpAddress());
        sb.append(", resourcePath=");
        sb.append(adminEvent.getResourcePath());

        if (adminEvent.getError() != null) {
            sb.append(", error=");
            sb.append(adminEvent.getError());
        }
        
        return sb.toString();
    }
    
    @Override
    public void close() {
    }

}

In this example, the SysoutEventListenerProvider logs events to the console. It filters out events based on the exclusion lists provided during initialization.

EventListenerProviderFactory Interface

This interface is a factory to create instances of the EventListenerProvider. It has the following methods:

  1. create(KeycloakSession session): Creates an instance of the EventListenerProvider. You’ll typically instantiate your custom provider here.
  2. init(Config.Scope config): Initializes the factory with configuration parameters provided by Keycloak. This method is used to set up any required configurations.
  3. postInit(KeycloakSessionFactory factory): Called after all factories have been initialized. Useful for performing any post-initialization tasks.
  4. close(): Cleans up any resources used by the factory.
  5. getId(): Returns a unique identifier for the factory.

Here is a sample implementation:

public class SysoutEventListenerProviderFactory implements EventListenerProviderFactory {

    private Set<EventType> excludedEvents;
    private Set<OperationType> excludedAdminOperations;

    @Override
    public EventListenerProvider create(KeycloakSession session) {
        return new SysoutEventListenerProvider(excludedEvents, excludedAdminOperations);
    }

    @Override
    public void init(Config.Scope config) {
        String[] excludes = config.getArray("exclude-events");
        if (excludes != null) {
            excludedEvents = new HashSet<>();
            for (String e : excludes) {
                excludedEvents.add(EventType.valueOf(e));
            }
        }
        
        String[] excludesOperations = config.getArray("excludesOperations");
        if (excludesOperations != null) {
            excludedAdminOperations = new HashSet<>();
            for (String e : excludesOperations) {
                excludedAdminOperations.add(OperationType.valueOf(e));
            }
        }
    }

    @Override
    public void postInit(KeycloakSessionFactory factory) {

    }
    @Override
    public void close() {
    }

    @Override
    public String getId() {
        return "sysout";
    }

}

Installing the Provider on Keycloak

In order to install a provider, you need to copy its implementation (in the JAR file) into the providers folder of your Keycloak distribution. For example:

cp target/event-listener-sysout.jar /opt/keycloak-24.0.1/providers/

Then, start Keycloak. You can optionally include a list of events which you don’t want to capture with your listener. For example, to exclude the REFRESH_TOKEN and CODE_TO_TOKEN events, you can add them to the spi-events-listener-sysout-exclude-events option:

./kc.sh start-dev --spi-events-listener-sysout-exclude-events=CODE_TO_TOKEN,REFRESH_TOKEN

Enabling the Event

Lastly, you need to enable the Event Listener. In order to do so, from the Keycloak Console select Realm Settings and the Events Tab. Add the sysout Event Listener to your List of Events:

how to create a keycloak provider

Click Save.

Now, when you produce events such as login/logout or other admin events you will see that captured by your Event Listener which prints it on the console:

ADMIN EVENT: operationType=UPDATE, realmId=f305d6c0-df99-416a-9f7d-cdab4b90a41f, clientId=15cbfc55-9b70-439b-8a46-22805a3bf295, userId=d68698b6-e924-4fdb-b8ee-ca3d97113ff3, ipAddress=127.0.0.1, resourcePath=events/config

Conclusion

Overall, Keycloak providers are powerful tools for extending the platform’s capabilities and tailoring it to your specific use case. This article discussed how to build and deploy a sample Logging Listener in your Keycloak distribution.

Found the article helpful? if so please follow us on Socials