Call getUserByUsername returns null if the user is created by an external service in the same flow (cache or transaction problem?)

Hi all,

I have created a custom AbstractIdpAuthenticator to be used as FirstLoginFlow for my External IDP configuration.
In the action method I need to call an external service that (among other things) it takes care of creating the user in Keycloak if it does not exist.
And, if the result is SUCCESS, I need (in this custom first login flow) to get the user (byUsername) and set it as the authenticated user.

This is the code:

@Override
public void action(AuthenticationFlowContext context) {
    BrokeredIdentityContext brokeredContext = ...;
    // Call to the external system to create the user on Keycloak (if it doesn't exist)
    // and if all goes well, then:
    UserModel user = context.getSession().users().getUserByUsername(context.getRealm(), 
    brokeredContext.getModelUsername());
context.clearUser();
    context.setUser(user);
    context.getAuthenticationSession().setAuthenticatedUser(user);
    context.success();
}

The problem is that the user is always null, even though I can see in the DB that the user exists.
It seems something like the getUserByUsername is done on some “cached” users, or the current transaction cannot see the new user (even if it exists in the DB).
In the same way, when the external service is updating an existing user (instead of creating it), the getUserByUsername can retrieve the user but with the “old” values (for example, for First Name and Last Name).

Debugging it, I can see that the
context.getSession().getTransactionManager().isActive()
is TRUE.

Does someone know why this problem happen and how to fix it?

After my investigation I found the reason for this problem.
The problem was the configuration of isolation level on my DB that is set to REPEATABLE-READ.
In this action method, KCL already opened a new transcation, so a “snapshot” of the DB is taken. After the external call, the snapshot is the same old one. And this is the reason why I was not able to get the user within this “old” transaction (even with both HQL/SQL query select).

I fixed it using this code:

@Override
public void action(AuthenticationFlowContext context) {
    BrokeredIdentityContext brokeredContext = ...;
    // Call to the external system to create the user on Keycloak (if it doesn't exist)
    // and if all goes well, then:

    // Create a new KeycloakSession, used to get the user updated/created
    KeycloakSession newSession = context.getSession().getKeycloakSessionFactory().create();
    // Set the new session as KeycloakSession
    KeycloakSession oldSession = KeycloakSessionUtil.setKeycloakSession(newSession);
    // Start new transaction for this new session
    newSession.getTransactionManager().begin();
    // Get the updated/created user using the new session
    UserModel user = getUserModelByContext(context.getRealm(), newSession, brokeredContext);
    // Commit the transaction for the new session
    newSession.getTransactionManager().commit();
    // Restore the old KeycloakSession
    KeycloakSessionUtil.setKeycloakSession(oldSession);
    
    context.clearUser();
    context.setUser(user);
    context.getAuthenticationSession().setAuthenticatedUser(user);
    context.success();
}