I’ve created a custom even listener that calls an external API service on the following event/resource/types:
- onEvent(Event event)-> EventType.REGISTER
- onEvent(Event event) -> EventType.VERIFY_EMAIL
- onEvent(AdminEvent adminEvent, boolean b) -> ResourceType.USER + OperationType.CREATE
The admin event is executed when creating a new user through the Keycloak admin client and the other are called through normal browser registration flows on Keycloak.
The listener does a POST call on each of these events and passes along the UUID of the new user.
The external API service receives the UUID and creates an internal user/profile in my system.
The problem I seem to have is that the user is not yet available when my API service receives the UUID from the event listener in Keycloak. I always get a 404 not found on the following code.
This seems to happen when executing a call on the EventType.REGISTER event and the admin event.
UserRepresentation userRepresentation = realmResource.users().get(uuid).toRepresentation();
The call after the EventType.VERIFY_EMAIL event seems to be working, I suppose it’s because the user was created during the EventType.REGISTER stage.
It’s like there is a flush or something happening after the event listener has finished it’s call and only then the user is available through the admin client.
Does somebody know this is normal behavior or a workaround for this.
Ok, never mind. I’ve found a solution. Stupid really.
So I pass the KeycloakSession to the provider when creating it in the EventListenerProviderFactory.
Before I call my API I execute the following
session.getTransactionManager().commit() -> THIS IS NOT GOOD
It wasn’t until I asked the question that I finally saw the answer, same old story.
So I needed to take a whole other approach to this problem because when using the browser flow I got the following error at some point -> Cannot access delegate without a transaction
I ended up creating a custom transaction that extends AbstractKeycloakTransaction
In this transaction I do my API call to my own service.
In the provider I can use CustomTransaction as follows
CustomTransaction customTransaction = new CustomTransaction(data);
This is the correct way of doing it, I can on rollback also easily trigger an API call in my custom transaction when something goes wrong.
I’m playing around a bit with the KeycloakTransactionManager now to see what fits my needs best to ensure a proper flow.
Instead of enlistAfterCompletion I ended up using enlistPrepare because this actually rolls back actions made when my API calls fails.
User registers -> confirmation email is sent -> user click email confirmation link -> enlistPrepare API call is made and fails -> user stays not verified
When using enlist() and enlistAfterCompletion() the user is verified which I don’t want because I need the API call to be successful to prepare some user stuff in my own service before the user is redirect back to my app.
Hey, thanks for the post! I want to implement the same thing: send a post to external API when the user is created on Keycloak to create it on another system.
Could you please provide little bit more information on how you did it? Where these event are emitted?
I’ve put an example in the spi-event-listener example in the https://github.com/zonaut/keycloak-extensions repo.
Just as extra information I want to tell you that I choose another approach for my application.
I only create the user in my own system now the moment a valid registered user visits my app for the first time. I just do a check if the user exists, if not create it. This keeps my own DB clean until a user actually finished registration and visits the app. The UUID of the user is in the token so I can easily use that as id to create that user in my own DB.
I’ve seem that repo! Awesome work, it really helped me, thanks. I think Events will do fine for me, but I think I will need to run a background job in order to do the operations I need to do outside Keycloak, or it might affect performance.
Hi, thank you for info and sharing repo. You explained in your current solution, you create user when he visits your app
for the first time. We built the same solution as you in the past but problem is that we don’t know when a http request from user arrives, if it is first time or not. So we ended up checking it with every request forever which is a huge waste. I am curious in your case , how do you realise it is first http request with querying database every time?
when the user enters my app domain I always retrieve the users profile details and/or preferences.
When no such details are present I know no user has been created yet for that UUID in the token.
So it’s not like I check on each http request if the user exists, only when the user enter the app domain and on the get user details call or something like that which happens only once anyway in my app.
How do i create a custom transaction? Can i use the custom transaction inside onEvent method inside the listner to fetch other userInfo ?
Hi @zonaut Your repo was super useful!! Thanks a ton!!
I’ve implemented something similar on REGISTER event, however when i throw the RuntimeException from my TransactionManager the form results in a 500 error page.
How can i be able to display an error message to the customer?
More info in this thread Rollback user on Registration Event result in 500 - How to display error message?