keycloakuser-managementkeycloak-rest-api

How to generate and use login action token for Keycloak user update profile in external mail template


Our backend is currently using the KeyCloak Admin Client API (Java) to

Our flow however needs to support the following scenario :

So the questions is can I use the KeyCloak API (or some other mechanism) to generate login actions URLs for a user inside a keycloak realm that we can then use in an external email template to send out a complete registration email ?


Solution

  • You can implement custom service providers by implementing a particular service provider interface. For your case, you could implement a custom resource provider that returns an action token based on some input parameters.

    Implementation instructions for a custom REST API endpoint are provided in the documentation. You can also check this article, that pretty much covers your scenario.

    I played around with it a bit, here's an example:

    public class ExecuteActionsTokenResourceProvider implements RealmResourceProvider {
    
        private static final Logger log = Logger.getLogger(ExecuteActionsTokenResourceProvider.class);
    
        private final KeycloakSession session;
    
        public ExecuteActionsTokenResourceProvider(KeycloakSession session) {
            this.session = session;
        }
    
        @POST
        @Path("action-tokens")
        @Produces({MediaType.APPLICATION_JSON})
        public Response getActionToken(
            @QueryParam("userId") String userId,
            @QueryParam("email") String email,
            @QueryParam("redirectUri") String redirectUri,
            @QueryParam("clientId") String clientId,
            @Context UriInfo uriInfo) {
    
            KeycloakContext context = session.getContext();
            RealmModel realm = context.getRealm();
            int validityInSecs = realm.getActionTokenGeneratedByUserLifespan();
            int absoluteExpirationInSecs = Time.currentTime() + validityInSecs;
    
            ClientModel client = assertValidClient(clientId, realm);
    
            assertValidRedirectUri(redirectUri, client);
    
            // Can parameterize this as well
            List requiredActions = new LinkedList();
            requiredActions.add(RequiredAction.UPDATE_PASSWORD.name());
    
            String token = new ExecuteActionsActionToken(
                userId,
                absoluteExpirationInSecs,
                requiredActions,
                redirectUri,
                clientId
            ).serialize(
                session,
                context.getRealm(),
                uriInfo
            );
    
            return Response.status(200).entity(token).build();
        }
    
        private void assertValidRedirectUri(String redirectUri, ClientModel client) {
            String redirect = RedirectUtils.verifyRedirectUri(session, redirectUri, client);
            if (redirect == null) {
                throw new WebApplicationException(
                    ErrorResponse.error("Invalid redirect uri.", Status.BAD_REQUEST));
            }
        }
    
        private ClientModel assertValidClient(String clientId, RealmModel realm) {
            ClientModel client = realm.getClientByClientId(clientId);
            if (client == null) {
                log.debugf("Client %s doesn't exist", clientId);
                throw new WebApplicationException(
                    ErrorResponse.error("Client doesn't exist", Status.BAD_REQUEST));
            }
            if (!client.isEnabled()) {
                log.debugf("Client %s is not enabled", clientId);
                throw new WebApplicationException(
                        ErrorResponse.error("Client is not enabled", Status.BAD_REQUEST));
            }
            return client;
        }
    
        @Override
        public Object getResource() {
            return this;
        }
    
        @Override
        public void close() {
            // Nothing to close.
        }
    }
    

    You would then package this class in a JAR and deploy it to Keycloak, like described in the deployment documentation. There are also lots of examples of this on Github.