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.