2022-04-21 // Keycloak Script Providers
Documentation
See:
Server Developer Guide - Service Provider Interfaces (SPI) - JavaScript providers
GitHub Keycloak Script Providers Repository
on how to prepare the Keycloak script providers for deployment into a Keycloak instance.
Protocol Mapper
With the so-called protocol mapper, Keycloak offers a method for the mapping of arbitrary attributes onto a specific authentication protocol (e.g. OpenID Connect) and its protocol specific attributes (e.g. Claims in OpenID Connect).
Script Mapper
If, for a specific use-case, there is no built-in protocol mapper available in Keycloak, it is possible to implement a protocol mapper in JavaScript. Such a script mapper will be running within the Keycloak application and will be executed via the Java Nashorn scripting interface.
The possibilities for what can be achieved with a script mapper are numerous. The following will only show the general approach with a basic example. It implements a recursive mapper for a role attribute (in this case the attribute policy
) or rather its values onto the OpenID Connect attribute policy
.
Unfortunately both the role attribute policy
as well as the OpenID Connect attribute policy
are currently hard coded in the script mapper, since i couldn't figure out a way to dynamically pass a value from the Keycloak configuration of the script mapper. Such a functionality seems to be only available for protocol mappers implemented in Java.
Development and Test
On a Keycloak test or development system edit the file:
vi <PATH_TO_KEYCLOAK>/standalone/configuration/profile.properties
and set the following parameters:
feature.scripts=enabled feature.upload_scripts=enabled
restart the Keycloak application.
After this the different mapper dialogs within the WebUI of the Keycloak application show an additional entry Script Mapper
in the drop-down menu Mapper Type
. If the Script Mapper
entry is selected, a editor dialog will be presented which can be used for the development and testing of the script mapper.
Attention: After finishing the development and testing of the script mapper the parameter feature.upload_scripts
shown above should be disabled again, since it poses a security risk!
Deployment
For the actual deployment of the previously developed script mapper onto a production Keycloak system the following preparation steps are necessary:
Directory creation:
mkdir -p /tmp/script_provider/role_attribute_mapper_policy/META-INF/
Copy the source code of the newly developed script mapper and save it into a file:
vi /tmp/script_provider/role_attribute_mapper_policy/role_attribute_mapper_policy.js
File contents:
/** * Available variables: * user - the current user * realm - the current realm * token - the current token * userSession - the current userSession * keycloakSession - the current keycloakSession */ /** * Change value to "true" in order to enable debugging * output to /var/log/keycloak/keycloak.log. */ var debug = false var ArrayList = Java.type("java.util.ArrayList"); var policies = new ArrayList(); /** * The actual debug output function */ function debugOutput(msg) { if (debug) print("Debug script mapper: " + msg); } /** * Helper function to determine **all** roles assigned * to a given user, even the indirectly assigned ones * (e.g. through group memberships). The built-in method * "getRealmRoleMappings" does unfortunately not work * here, since it only returns the **directly** assigned * roles (see: https://www.keycloak.org/docs-api/<version>/javadocs/org/keycloak/models/RoleMapperModel.html#getRealmRoleMappings-- * for details). */ function getAllUserRoles() { var userRoles = []; realm.getRoles().forEach(function(role) { if(user.hasRole(role)) { debugOutput('found role "' + role.getName() + '" assigned to user.'); userRoles.push(role); } else { debugOutput('role "' + role.getName() + '" is not assigned to user.'); } }); return userRoles; } /** * Check all roles assigned to a given user for a "policy" * role attribute. If the role attribute is present, split * the value into individual elements and insert each element * into the array to be returned. */ var roles = getAllUserRoles(); roles.forEach(function(role) { var policy = role.getAttribute("policy"); if (policy.length) { debugOutput('attribute "policy" found in role ' + role.getName()); policy.forEach(function(value) { var arrayOfValues = value.replace(/, /g, ',').split(','); arrayOfValues.forEach(function(value) { debugOutput('adding "policy" attribute value ' + value + ' to array'); if (policies.indexOf(value) < 0) policies.add(value); }); }); } }); /** * Return the array populated above to Keycloak */ debugOutput('final "policy" array ' + policies); exports = policies;
Create a deployment descriptor for the script mapper:
vi /tmp/script_provider/role_attribute_mapper_policy/META-INF/keycloak-scripts.json
File contents:
{ "mappers": [ { "name": "Role Attribute Mapper - policy", "fileName": "role_attribute_mapper_policy.js", "description": "Maps the 'policy' role attribute to the OIDC token of a user" } ] }
Pack the script mapper and the deployment descriptor into a deployable JAR file:
cd /tmp/script_provider/role_attribute_mapper_policy/ zip -r role_attribute_mapper_policy.jar META-INF/ role_attribute_mapper_policy.js
On the Keycloak system edit the file:
vi <PATH_TO_KEYCLOAK>/standalone/configuration/profile.properties
and set the following parameters:
feature.scripts=enabled
restart the Keycloak application.
Put the JAR file created above into the directory <PATH_TO_KEYCLOAK>/standalone/deployments/
. The deployment of the script mapper into the Keycloak application should happen automatically. This can be verified in the Keycloak log file by checking for log lines like:
INFO [org.jboss.as.repository] (DeploymentScanner-threads - 2) WFLYDR0001: Content added at location <PATH_TO_KEYCLOAK>/standalone/data/content/e1/714bbd9b178cd2004d0a2f999584030f06a54c/content INFO [org.jboss.as.server.deployment] (MSC service thread 1-1) WFLYSRV0027: Starting deployment of "role_attribute_mapper_policy.jar" (runtime-name: "role_attribute_mapper_policy.jar") INFO [org.keycloak.subsystem.server.extension.KeycloakProviderDeploymentProcessor] (MSC service thread 1-2) Deploying Keycloak provider: role_attribute_mapper_policy.jar INFO [org.jboss.as.server] (DeploymentScanner-threads - 2) WFLYSRV0010: Deployed "role_attribute_mapper_policy.jar" (runtime-name : "role_attribute_mapper_policy.jar")
Within the Keycloak application the availability of the new script mapper can be verified with:
Admin
→Server Info
→Providers
→protocol-mapper
→script-role_attribute_mapper_policy.js
or in one of the different mapper dialogs within whe WebUI of the Keycloak application in the drop-down menu
Mapper Type
as a new entryRole Attribute Mapper - policy