How to configure KeyCloak with External User Provider so that it does not immediately call getUserByUsername method after addUser method

We are using KeyCloak for Identity and Access Management for our website. I have also implemented the UserStorageProvider interface for authenticating the user from an external user provider. Now, I am trying to implement the UserRegistrationProvider interface to register a user with that external user provider.

I have implemented the interface and the addUser method which calls the external provider’s API via a RestTemplate.

When registering a new user, my custom implementation of addUser is called and the rest API of the external provider is also called returning a message

“Your username is pending confirmation. An email will be sent to confirm your registration”

After that, the user receives an email to confirm the registration.

But, the problem I am facing is that after addUser method is called, KeyCloak calls the getUserByUsername method to log in, but the user is not yet registered because email verification is pending. So, it throws an error.

Ideally, the flow should be that after calling addUser method, KeyCloak should not call the getUserByUsername method and redirect to a custom page that shows the message received from the provider on the screen.

Below is the implementation of addUser method :

@Override
public UserModel addUser(RealmModel realmModel, String s) {

    //Getting additional attributes from the form
    MultivaluedMap<String, String> attributes = session.getContext().getContextObject(HttpRequest.class)
            .getDecodedFormParameters();

    //Some code to map the attributes to the POJO
    .......
    //
    Usuario usuario = new Usuario(datosContacto,datosPersonales,datosCredenciales);

    //Calling repository method which then calls the RestTemplate
    // ABCUserAdaptor is a custom class which extends AbstractUserAdapterFederatedStorage
    // And ABCUser is a custom POJO

    ABCUser user = repository.saveUser(usuario);
    ABCUserAdapter abcUserAdapter = new ABCUserAdapter(session, realmModel, model, user);
    abcUserAdapter.setEnabled(false);
    return abcUserAdapter;
}

I tried returning null from addUser method expecting to get the desired flow. But, it resulted in KeyCloak saving the user in its database and logging in with the credentials.

Thank you in advance for replying, if there is anything else that should be included for the reference please tell me, I will add it.

So, after researching for some days, I found a solution to my problem.

After referring to the KeyCloak’s Source Code, I found out that .ftl files are rendered by a Provider Interface in my case LoginFormsProvider interface. FreeMarkerLoginFormsProvider is the class that implements the interface and has a method defined as createForm. This method creates a form from the FTL file and returns it as a Response object.

Now, addUser returns a UserModel object and the flow continues to go to the getUserByUsername method. To stop this flow I threw an AuthenticationFlowException with the form in Response.

Here’s the logic:

@Override
public UserModel addUser(RealmModel realmModel, String s) {

    // Getting additional attributes from the form
    MultivaluedMap<String, String> attributes = session.getContext().getContextObject(HttpRequest.class)
            .getDecodedFormParameters();

    // Some code to map the attributes to the POJO
    .......
    //
    Usuario usuario = new Usuario(datosContacto,datosPersonales,datosCredenciales);

    // Calling repository method which then calls the RestTemplate
    // saveUser now returns a string containing the response message from the rest client

    String response = repository.saveUser(usuario);

    // An object of FreeMarkerUtil class to pass as an argument 
    // to FreeMarkerLoginFormsProvider's constructor
    FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
    // Initializing FreeMarkerLoginFormsProvider's object
    FreeMarkerLoginFormsProvider freeMarkerLoginFormsProvider = new FreeMarkerLoginFormsProvider(session,freeMarkerUtil);
    // Name of the FTL file to render custom page
    String templateName = "login-verify-email.ftl";

    // Setting form attribute - response from the rest client
    // To show on the page
    LoginFormsProvider formsProvider = freeMarkerLoginFormsProvider.setAttribute("messageResponse", response);

    // Throw AuthenticationFlowException to stop the flow and redirect to a custom page 
    throw new AuthenticationFlowException(AuthenticationFlowError.UNKNOWN_USER, formsProvider.createForm(templateName));
}

This is a solution that I can find for now. It may be wrong or there can be a much more efficient way to achieve this. Let me know if there are any.