Keycloak Event Listeners are extensions that allow you to react to events happening within Keycloak, such as user logins, role assignments, and other administrative actions. By implementing custom event listeners, you can enhance your Identity and Access Management (IAM) system with features like custom audit logging and integration with external systems via webhooks.
What is Keycloak Event Listeners?
Keycloak Event Listeners are components that enable you to hook into the event system of Keycloak. They allow you to execute custom logic whenever certain events occur. This can be incredibly useful for logging, alerting, or integrating with other systems.
How do you implement Keycloak Event Listeners?
To implement Keycloak Event Listeners, you need to create a custom Java class that implements the EventListenerProviderFactory interface. You then package this class as a JAR file and deploy it to your Keycloak server.
Step-by-Step Guide to Implementing Keycloak Event Listeners
1. Set Up Your Development Environment
Ensure you have the following tools installed:
- JDK 11 or later
- Maven
- Keycloak server
2. Create a New Maven Project
Create a new Maven project structure:
mvn archetype:generate -DgroupId=com.example -DartifactId=keycloak-event-listener -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
Navigate to the project directory:
cd keycloak-event-listener
3. Add Dependencies
Edit the pom.xml file to include Keycloak dependencies:
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<version>21.1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
<version>21.1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.4.2.Final</version>
<scope>provided</scope>
</dependency>
</dependencies>
4. Implement the Event Listener Provider Factory
Create a new Java class CustomEventListenerProviderFactory.java in src/main/java/com/example:
package com.example;
import org.keycloak.Config;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
public class CustomEventListenerProviderFactory implements EventListenerProviderFactory {
public static final String ID = "custom-event-listener";
@Override
public String getId() {
return ID;
}
@Override
public EventListenerProvider create(KeycloakSession session) {
return new CustomEventListenerProvider(session);
}
@Override
public void init(Config.Scope config) {
// Initialization code here
}
@Override
public void postInit(KeycloakSessionFactory factory) {
// Post-initialization code here
}
@Override
public void close() {
// Cleanup code here
}
}
5. Implement the Event Listener Provider
Create another Java class CustomEventListenerProvider.java in the same package:
package com.example;
import org.keycloak.Config;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CustomEventListenerProvider implements EventListenerProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomEventListenerProvider.class);
private final KeycloakSession session;
public CustomEventListenerProvider(KeycloakSession session) {
this.session = session;
}
@Override
public void onEvent(Event event) {
if (event.getType() == EventType.LOGIN) {
logger.info("User login detected: {}", event.getDetail("username"));
} else if (event.getType() == EventType.LOGOUT) {
logger.info("User logout detected: {}", event.getDetail("username"));
}
}
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
if (event.getOperationType() == OperationType.CREATE) {
logger.info("Admin event CREATE detected: {}", event.getResourcePath());
} else if (event.getOperationType() == OperationType.DELETE) {
logger.info("Admin event DELETE detected: {}", event.getResourcePath());
}
}
@Override
public void close() {
// Cleanup code here
}
}
6. Package the Event Listener
Build the project to create a JAR file:
mvn clean package
The JAR file will be located in the target directory.
7. Deploy the Event Listener to Keycloak
Copy the JAR file to the providers directory of your Keycloak server:
cp target/keycloak-event-listener-1.0-SNAPSHOT.jar /path/to/keycloak/standalone/deployments/
Restart the Keycloak server to load the new provider.
8. Configure the Event Listener
Log in to the Keycloak Admin Console and navigate to the realm settings. Under the “Events Config” tab, add your custom event listener to the “Configured Event Listeners” list.
Quick Answer
To implement Keycloak Event Listeners, create a Java class implementing EventListenerProviderFactory, package it as a JAR, and deploy it to your Keycloak server. Configure the event listener in the Keycloak Admin Console to start capturing events.
How do you set up custom audit logging with Keycloak Event Listeners?
Custom audit logging involves capturing and storing event data for auditing purposes. You can achieve this by extending the EventListenerProvider class and writing logic to log events to a file or a database.
Example: Logging Events to a File
Modify the CustomEventListenerProvider class to log events to a file:
package com.example;
import org.keycloak.Config;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CustomEventListenerProvider implements EventListenerProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomEventListenerProvider.class);
private final KeycloakSession session;
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public CustomEventListenerProvider(KeycloakSession session) {
this.session = session;
}
@Override
public void onEvent(Event event) {
String logMessage = String.format("%s - %s - %s - %s",
dateFormat.format(new Date()),
event.getType(),
event.getRealmId(),
event.getDetail("username"));
logToFile(logMessage);
}
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
String logMessage = String.format("%s - %s - %s - %s - %s",
dateFormat.format(new Date()),
event.getOperationType(),
event.getResourcePath(),
event.getRealmId(),
event.getAuthDetails().getUserId());
logToFile(logMessage);
}
private void logToFile(String message) {
try (FileWriter writer = new FileWriter("/var/log/keycloak/events.log", true)) {
writer.write(message + "\n");
} catch (IOException e) {
logger.error("Failed to write to log file", e);
}
}
@Override
public void close() {
// Cleanup code here
}
}
Key Takeaways
- Extend
EventListenerProviderto capture events. - Use file I/O to log events to a file.
- Ensure proper error handling to avoid data loss.
How do you send events to a webhook using Keycloak Event Listeners?
Sending events to a webhook involves making HTTP requests to an external URL whenever an event occurs. This can be useful for integrating Keycloak with other systems like Slack, PagerDuty, or a custom notification service.
Example: Sending Events to a Webhook
Modify the CustomEventListenerProvider class to send events to a webhook:
package com.example;
import org.keycloak.Config;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CustomEventListenerProvider implements EventListenerProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomEventListenerProvider.class);
private final KeycloakSession session;
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private final Client client = ClientBuilder.newClient();
private final String webhookUrl = "https://example.com/webhook";
public CustomEventListenerProvider(KeycloakSession session) {
this.session = session;
}
@Override
public void onEvent(Event event) {
String payload = String.format("{\"type\": \"%s\", \"realm\": \"%s\", \"username\": \"%s\", \"timestamp\": \"%s\"}",
event.getType(),
event.getRealmId(),
event.getDetail("username"),
dateFormat.format(new Date()));
sendWebhook(payload);
}
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
String payload = String.format("{\"operation\": \"%s\", \"resource\": \"%s\", \"realm\": \"%s\", \"userId\": \"%s\", \"timestamp\": \"%s\"}",
event.getOperationType(),
event.getResourcePath(),
event.getRealmId(),
event.getAuthDetails().getUserId(),
dateFormat.format(new Date()));
sendWebhook(payload);
}
private void sendWebhook(String payload) {
Response response = client.target(webhookUrl)
.request(MediaType.APPLICATION_JSON)
.post(Entity.entity(payload, MediaType.APPLICATION_JSON));
if (response.getStatus() != 200) {
logger.error("Failed to send webhook: {}", response.getStatusInfo());
}
}
@Override
public void close() {
client.close();
}
}
Key Takeaways
- Use JAX-RS client to send HTTP requests.
- Format the payload as JSON for compatibility with most webhooks.
- Handle HTTP errors to ensure reliability.
What are the security considerations for Keycloak Event Listeners?
When implementing Keycloak Event Listeners, it’s crucial to consider security to prevent unauthorized access and data leakage.
Secure Configuration
- Environment Variables: Store sensitive information like webhook URLs and API keys in environment variables or secure vaults.
- HTTPS: Ensure that all communication with external systems is done over HTTPS to protect data in transit.
Error Handling
- Logging: Avoid logging sensitive information such as passwords or tokens.
- Error Responses: Handle HTTP errors gracefully to prevent exposing internal server details.
Access Control
- Permissions: Restrict access to the event listener configuration to authorized personnel only.
- Authentication: Ensure that webhooks are protected with authentication mechanisms like API keys or OAuth tokens.
Comparison Table: Custom Audit Logging vs. Webhooks
| Approach | Pros | Cons | Use When |
|---|---|---|---|
| Custom Audit Logging | Easier to control data storage | Requires additional infrastructure for log management | You need to store logs for auditing purposes |
| Webhooks | Real-time integration with external systems | Dependent on external service availability | You want to trigger actions in response to events |
Quick Reference
📋 Quick Reference
mvn clean package- Build the project and create a JAR file.cp target/keycloak-event-listener-1.0-SNAPSHOT.jar /path/to/keycloak/standalone/deployments/- Deploy the JAR to Keycloak.logger.info("Message")- Log messages using SLF4J.client.target(url).request(MediaType.APPLICATION_JSON).post(Entity.entity(payload, MediaType.APPLICATION_JSON))- Send a POST request to a webhook.
Troubleshooting Common Issues
Issue: Event Listener Not Triggering
Symptom: Events are not being logged or sent to the webhook.
Solution: Ensure that the event listener is correctly configured in the Keycloak Admin Console. Check the server logs for any errors related to the event listener.
Issue: Sensitive Data in Logs
Symptom: Logs contain sensitive information like passwords.
Solution: Avoid logging sensitive data. Use placeholders or obfuscate sensitive information before logging.
Issue: Webhook Fails with 404 Error
Symptom: Webhook requests fail with a 404 error.
Solution: Verify that the webhook URL is correct and that the endpoint is accessible from the Keycloak server.
Final Thoughts
Implementing Keycloak Event Listeners allows you to extend the functionality of your IAM system with custom audit logging and webhooks. By following the steps outlined in this guide, you can create robust event-driven solutions that enhance security and integration capabilities.
That’s it. Simple, secure, works. Go ahead and implement these listeners in your Keycloak setup today.

