0

We implemented a keycloak SPI to perform lookups/updates on an external user database.

One of the features we wanted to implement is to trigger an email when users reach a certain number of login attempts. This is something we did in pre-keycloak version of our application and attempting to implement this feature.

The guidance is to use the execute-actions-email admin REST end point by supplying the "UPDATE_PASSWORD" required action along with other inputs. But I consistently get a 403 response.

I was looking around if in the custom SPI there is a way to use the Keycloak Java API instead. I found an implementation of the executeActionsEmail method in UserResource. But this needs some additional initializations (mostly admin privileges I think). I'm stuck here as well.

I'm looking for any working example(s) that would help me understand how to implement this use case. Postman, Java API any thing would be highly appreciated.

Thank you,

javacup
  • 45
  • 1
  • 1
  • 9
  • 1
    Does you user have the privilege to manage users? It is needed to call that endpoint. – sventorben Aug 26 '21 at 11:33
  • In postman, used the master realm and the admin username-pwd with password grant_type to make this work. I'm able to fetch an access token and use it to execute the email end point. Next question, I tried making the admin-cli client confidential in the master realm and used the secret to fetch a token using client_credentials grant_type. I got the token, and used it to execute the email end point, it failed with a 403. reverted to the password grant_type and it worked. Any ideas how to make it work for client_credentials grant_type? – javacup Aug 26 '21 at 17:40
  • I just assumed the admin user to have privileges to manage users. Where can I see that setting in keycloak? – javacup Aug 26 '21 at 17:42
  • I got it to work in Postman. Next I'd like to do the same in a CustomUserStorageProvider after the user reaches limit on password attempts. But I'm having trouble correctly initializing the Keycloak java admin client using the KeycloakBuilder: `ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-1) Uncaught server error: java.lang.ExceptionInInitializerError at org.keycloak.admin.client.Keycloak.newRestEasyClient(Keycloak.java:66)` – javacup Aug 27 '21 at 01:06
  • `KeycloakContext context = ksession.getContext(); UriInfo backendUriInfo = context.getUri(UrlType.BACKEND); String backendBaseUri = backendUriInfo.getBaseUri().toString(); Keycloak keycloak = KeycloakBuilder.builder().serverUrl(backendBaseUri).realm("master") .clientId("admin-cli").grantType("password").username("admin").password("****").build();` – javacup Aug 27 '21 at 01:07
  • I finally was able to get it to work. I enabled the keycloak-core as a global module in standalone.xml otherwise, keycloak would not pick up the dependencies for the SPI project that I deployed. I also installed a keycloak-admin-client module (not installed with the standard keycloak distribution.. its beyond me what that is the case) and made that a global module as well. In the custom storage provider I'm now able to trigger an outbound password reset email. – javacup Aug 27 '21 at 19:14

1 Answers1

0

This is the final solution:

    private boolean triggerExecuteActionsEmailAdminClient(RealmModel realm, UserModel user) {
    boolean executed = false;
    try {
        KeycloakContext context = keycloakSession.getContext();
        UriInfo backendUriInfo = context.getUri(UrlType.BACKEND);

        String backendBaseUri = backendUriInfo.getBaseUri().toString();
        log.info(String.format("backendBaseUri: %s", backendBaseUri));

        Keycloak keycloak = KeycloakBuilder.builder().serverUrl(backendBaseUri).realm("master")
                .clientId("admin-cli").grantType("password").username("admin").password("******")
                .resteasyClient(new ResteasyClientBuilder().connectionPoolSize(10).build()).build();
        String realmName = realm.getName();
        String userId = user.getId();

        keycloak.realm(realmName).users().get(userId).executeActionsEmail(Arrays.asList("UPDATE_PASSWORD"));
        executed = true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return executed;
}
standalone.xml in jboss:domain:ee subsystem
        <global-modules>
            <module name="org.keycloak.keycloak-core"/>
            <module name="org.keycloak.keycloak-admin-client"/>
        </global-modules>
In lieu of the above, I added the dependencies in the jboss deployment descriptor of the installed custom SPI jar file
<jboss-deployment-structure>
<deployment>
    <dependencies>
        <module name="org.springframework.security" />
        <module name="com.oracledatabase.oracle" />
        <module name="org.keycloak.keycloak-core"/>
        <module name="org.keycloak.keycloak-server-spi"/>
        <module name="org.keycloak.keycloak-server-spi-private"/>
        <module name="org.keycloak.keycloak-services"/>
        <module name="org.keycloak.keycloak-saml-core-public"/>
        <module name="org.keycloak.keycloak-admin-client"/>            
        <module name="org.jboss.logging"/>                      
    </dependencies>
</deployment>

Installing the keycloak-admin-client module, make sure keycloak-admin-client-14.0.0.jar is available in /tmp

sudo $KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.keycloak-admin-client --resources=/tmp/keycloak-admin-client-14.0.0.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-common,org.apache.httpcomponents,javax.ws.rs.api,org.jboss.resteasy.resteasy-jaxrs"
javacup
  • 45
  • 1
  • 1
  • 9