Skip to main content
Skip table of contents

Mapping SAML attributes to OIDC claims with Javascript

When the simple mapping and transform rules do not meet your requirements you can add a Javascript rule. This is a significantly more advanced operation though and an understanding of both scripting and SAML is... advantageous.

Writing a scripted rule

Considerations before you start

  1. Scripted rules are designed such that they can produce multiple OIDC claims, and you should not expect to need more than one per OpenAthens Keystone connection
  2. Execution time is limited to one second. After this time the script is stopped and the login attempt will fail before the user is passed to you
  3. Any exceptions generated by your script will stop execution and the login attempt will fail before the user is passed to you
  4. Input attributes from SAML can be multi-valued and are unordered

Global objects

At the point of execution the script has access to 3 global objects

  • response - encapsulates the SAML response from the IdP.
  • output - javascript object used to store OIDC claims.
  • logger - lets you write a limited number of debug statements.

Scripted ruleset example

JS
/**
 * parameter entitlements
 *      List of users entitlements
 * parameter roles
 *      List of users roles
 * return The membership type: gold, silver, bronze or tin.
 */
function getMembershipType(entitlements, roles) {
    logger.log("Entitlements: %s", entitlements);
    if (entitlements.includes("https://auth.example.com/terms-and-conditions")) {
        var rolesNoSuffix = removeScope(roles);
        logger.log("User has entitlement");
        logger.log("Roles without suffix: %s", rolesNoSuffix);
        if (rolesNoSuffix.includes("staff")) {
            logger.log("User is staff");
            return "gold";
        } else if (rolesNoSuffix.includes("student")) {
            logger.log("User is student");
            return "silver";
        } else if (role.includes("member")) {
            rolesNoSuffix.log("User is member");
            return "bronze";
        }
    }
    return "tin";
}

/**
 * parameter roles 
 *      List of users roles
 * return
 *      List of users roles with the scope (suffix) removed if present.
 *      E.g staff@idp.domain.com -> staff.
 */
function removeScope(roles) {
    return roles.map((value) => {
        var matches = value.match(/^(.*)@.*$/);
        if (matches) {
            return matches[1];
        } else {
            return value;
        }
    });
}

//Execution starts here.
var xp = '//saml:SubjectLocality/@Address[1]';

//Should return a single valued array.
var nodes = response.evalXPath(xp);

if (nodes) {
    logger.log("Found %d address attributes", nodes.length);
    //Assign userIp to the output.  Will be released as an OIDC claim
    output.userIp = nodes[0].textContent;
} else {
    logger.log("Could not get users IP address");
}

var entitlements = response.getAttributeValues("urn:oid:1.3.6.1.4.1.5923.1.1.1.7");
var roles = response.getAttributeValues("urn:oid:1.3.6.1.4.1.5923.1.1.1.9");
//Assign membershipType to the output.  Will be released as an OIDC claim
output.membershipType = getMembershipType(entitlements, roles);


Response

Public Methods

response.getIssuer();

Return Value: 

    • String identifying the issuer of the SAML response.

response.getAttributeValues(name);

Parameters:

    • name the name of the SAML Attribute.

Return value:

    • String[] containing all values associated with the given name.  
       The array will be empty if there is no Attribute in the response with the give name, or there are no values contained within the Attribute.

response.getFirstAttributeValue(name);

Parameters:

    • name the name of the SAML Attribute.

Return value:

    • string containing the first value associated with the given name (the order is indeterminate). 
      null will be returned if there is no Attribute with the given name, or there are no values contained within the Attribute.

response.evalXPath(exp);

The following xml namespaces are pre-registered

PrefixNamespace
samlp

urn:oasis:names:tc:SAML:2.0:protocol

saml

urn:oasis:names:tc:SAML:2.0:assertion

Therefore any elements within these namespaces can be access via the prefix. E.g //samlp:StatusCode/@Value.

If you require access to anything not defined in these namespaces you need to register them in the xpath expression. E.g //*[local-name()='ElemName' and namespace-uri()='http://ext.namespace.com']/@AttrName


Parameters:

    • exp the xpath expression which will be evaluated over the SAML response (xml)

Return value:

    • An Element[].  The array will be empty if nothing was found for the given xpath expression.

Logger

The logger is provided to enable script debugging and is limited to the last 100 messages.

Public Methods

logger.log(obj1 [, obj2, ..., objN]);

Parameters:

    • A list of java script object you wish to inspect.

logger.log(msg [, subst1, ..., substN]);

Parameters:

    • JavaScript objects with which to replace substitution strings within msg.
      E.g logger.log("SAML Issuer: %s", issuerString);

Output

A standard javascript Object which you can assign any arbitrary property and associated value (String or String[]). 

E.g If you wish to assert a OIDC claim indicating membershipType you would do the  following

JS
//Recommended method.
output.membershipType = 'gold';
//Or not recommended method
output['membershipType'] = 'silver';







JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.