Our backend is currently using the KeyCloak Admin Client API (Java) to
Our flow however needs to support the following scenario :
executeActionsEmail
API call and have Keycloak send out an email to users for them to complete their profile we would like to use an external email service / template to send out these mailsUPDATE_PROFILE
email it contains a /login-actions/action-token?key=eyJhbG…
link with an action-token.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 ?
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.