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
Comments
@Erwin: It works now as described, the mapper is visible in the UI! I accidentally deployed the script mapper javascript file in the META-INF/ folder, it should be outside.
Hi, I followed the steps but couldnt find the protocol-mapper in UI Keycloak version 21.0.2. Any help would be appreciated!
Leave a comment…
- E-Mail address will not be published.
- Formatting:
//italic// __underlined__
**bold**''preformatted''
- Links:
[[http://example.com]]
[[http://example.com|Link Text]] - Quotation:
> This is a quote. Don't forget the space in front of the text: "> "
- Code:
<code>This is unspecific source code</code>
<code [lang]>This is specifc [lang] code</code>
<code php><?php echo 'example'; ?></code>
Available: html, css, javascript, bash, cpp, … - Lists:
Indent your text by two spaces and use a * for
each unordered list item or a - for ordered ones.
Thanks. Have you been able to get this to work in Keycloak 18.0.0 (WildFly legacy build)? The script mapper feature.upload_scripts=enabled is deprecated. So I manage do manage to upload our Script Mapper (merge-role-attributes.js) with same name as it is still in the database for clients, but it does not show up in the UI nor is it called. Do you know of a way to activate the Script Mapper in Keycloak 18.0.0 (WildFly legacy build)?