bityard Blog

// 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:

  • AdminServer InfoProvidersprotocol-mapperscript-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 entry Role Attribute Mapper - policy

Comments

Erwin
No. 1 @ 2022/05/13 17:15

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)?

Erwin
No. 2 @ 2022/05/16 08:39

@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.

Keycloak User
No. 3 @ 2023/05/12 14:07

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…

N C U G O
  • 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.
This website uses cookies. By using the website, you agree with storing cookies on your computer. Also you acknowledge that you have read and understand our Privacy Policy. If you do not agree leave the website. More information about cookies