Keycloak, Tutorial, Java, Custom Event Listener

Keycloak - Custom Event Listener

Tutorial about how to create and deploy a custom event listener for Keycloak.

Author Martin Malfertheiner
Martin Malfertheiner23rd November 2020

This tutorial shows how you can create your very specific event listener in Keycloak. Keycloak let's you hook into almost 100 different event types. The example we're building in this tutorial will send an email to an admin on every new registration. In this way somebody of the company will be notified whenever a new user signs up. The repository of the example can be found here.

How to create the listener?

In order to implement a custom event listener you need to impement the org.keycloak.events.EventListenerProvider interface. The function onEvent will be called on every event that happens in Keycloak. In this example we check for the REGISTER event and send an email to an admin user. The class AdminUser (see here) implements the Keycloak user model to provide a hard coded email to which the event listener sends the notification.

@Override
public void onEvent(Event event) {

    if (EventType.REGISTER.equals(event.getType())) {
        log.infof("## NEW %s EVENT", event.getType());
        log.info("-----------------------------------------------------------");

        RealmModel realm = this.model.getRealm(event.getRealmId());
        UserModel newRegisteredUser = this.session.users().getUserById(event.getUserId(), realm);

        String emailPlainContent = "New user registration\n\n" +
                "Email: " + newRegisteredUser.getEmail() + "\n" +
                "Username: " + newRegisteredUser.getUsername() + "\n" +
                "Client: " + event.getClientId();

        String emailHtmlContent = "<h1>New user registration</h1>" +
                "<ul>" +
                "<li>Email: " + newRegisteredUser.getEmail() + "</li>" +
                "<li>Username: " + newRegisteredUser.getUsername() + "</li>" +
                "<li>Client: " + event.getClientId() + "</li>" +
                "</ul>";

        DefaultEmailSenderProvider senderProvider = new DefaultEmailSenderProvider(session);

        try {
            senderProvider.send(session.getContext().getRealm().getSmtpConfig(), new AdminUser(), "Keycloak - New Registration", emailPlainContent, emailHtmlContent);
        } catch (EmailException e) {
            log.error("Failed to send email", e);
        }
        log.info("-----------------------------------------------------------");
    }
}

In order to be able to register this custom event listener class in Keycloak you will need to implement org.keycloak.events.EventListenerProviderFactory interface and return the newly created class in the create method.

public class CustomEventListenerProviderFactory implements EventListenerProviderFactory {
    @Override
    public EventListenerProvider create(KeycloakSession keycloakSession) {
        return new CustomEventListenerProvider(keycloakSession);
    }

    @Override
    public void init(Config.Scope scope) {

    }

    @Override
    public void postInit(KeycloakSessionFactory keycloakSessionFactory) {

    }

    @Override
    public void close() {

    }

    @Override
    public String getId() {
        return "custom-event-listener";
    }
}

The final step before we can package our listener is to create the following file src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory. This will make sure that Keycloak can discover the new Listener.

it.aboutbits.CustomEventListenerProviderFactory

You can find the file in the example repository here.

Bonus: Using keycloak services within listener

If you would like to use services from keycloak within your listener, like the email service in the example, then you have to add this file src/main/resources/META-INF/jboss-deployment-structure.xml.

<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.keycloak.keycloak-services" />
        </dependencies>
    </deployment>
</jboss-deployment-structure>

How to deploy the artifact?

Once you created the artifact you have to copy the jar into your keycloak installation ($KEYCLOAK_DIR/standalone/deployments/). If you are using docker to run your Keycloak instance you can do the following:

version: "3.7"

services:
  keycloak:
    image: jboss/keycloak:11.0.3
    environment:
      DB_VENDOR: POSTGRES
      DB_ADDR: postgres
      DB_DATABASE: keycloak
      DB_SCHEMA: public
      DB_USER: keycloak
      DB_PASSWORD: password
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: password
    volumes:
      - ./custom-event-listener/target/custom-event-listener-0.0.1-SNAPSHOT.jar://opt/jboss/keycloak/standalone/deployments/custom-event-listener-0.0.1-SNAPSHOT.jar
    depends_on:
      - postgres
    ports:
      - 8080:8080

  postgres:
    image: postgres:12
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: password
    ports:
      - 5432:5432

  mailhog:
    image: mailhog/mailhog
    ports:
      - 8025:8025

How to enable the listener?

You're almost done. The listener should now be available in the list of event listeners. All you have to do now is to select the listener from the dropdown of available event listeners and save.

Register the event listener

To see a running example checkout the repository and follow the instructions in the readme.