PingOne Advanced Identity Cloud

Scripting environment

PingOne Advanced Identity Cloud supports scripts written in JavaScript.

The scripting environment implements a scripting engine for each of the context types that are executed on the server. There are two versions of the scripting engine: next-generation and legacy.

Access Java classes

Scripts can only import Java classes on the allowlist. PingOne Advanced Identity Cloud defines an allowlist per script type.

Legacy scripts

To access Java classes in a script, use the JavaImporter:

var fr = JavaImporter(
    org.forgerock.openam.auth.node.api.Action,
    javax.security.auth.callback.NameCallback
);

if (callbacks.isEmpty()) {
    action = fr.Action.send(
      new fr.NameCallback("Enter Your First Name"),
      new fr.NameCallback("Enter Your Last Name")
    ).build();
} else {
    nodeState.putShared("FirstName", callbacks.get(0).getName());
    nodeState.putShared("LastName", callbacks.get(1).getName());
    action = fr.Action.goTo("true").build();
}
javascript
View the Java class allowlist

To view the Java class allowlist for a particular context type:

  1. Get an access token for the appropriate realm with the appropriate scopes. Learn more in Get an access token.

  2. Run the following REST command:

    $ curl 'https://<tenant-env-fqdn>/am/json/global-config/services/scripting/contexts/<context-value>/engineConfiguration' \(1)
    --header 'authorization: Bearer <access-token>' (2)
    {
      "_id": "engineConfiguration",
      "_rev": "-733065873",
      "propertyNamePrefix": "esv.",
      "serverTimeout": 0,
      "useSecurityManager": true,
      "maxThreads": 50,
      "coreThreads": 10,
      "whiteList": [
        "com.google.common.collect.ImmutableList",
        "…​"
      ], (3)
      "idleTimeout": 60,
      "queueSize": 10,
      "blackList": [
        "java.lang.Class",
        "…​"
      ],
      "_type": {
        "_id": "engineConfiguration",
        "name": "Scripting engine configuration",
        "collection": false
      }
    }
    bash
    1 Replace <context-value> with appropriate context value for the script you are working on. Learn more about context values in Manage scripts over REST.
    2 Replace <access-token> with the access token.
    3 The whitelist field shows all the classes currently on the allowlist.
Add a missing Java class to the allowlist

If required, you can request to have Java classes added to the allowlist. Learn more in How do I get Java classes added to the allowlist in Advanced Identity Cloud for scripting purposes? in the Ping Identity Knowledge Base.

To reduce the need to allowlist Java classes, consider migrating your scripts to use the next-generation scripting engine, which includes enhanced built-in script bindings for accessing many common script operations and the ability to include third-party software with library scripts.

Next-generation scripts

To enhance security, the next-generation scripting engine doesn’t support a configurable allowlist for Java classes.

Instead, check if next-generation bindings provide the functionality you need or implement the functionality as a reusable library script.

For example, use the callbacksBuilder binding for callback functionality:

if (callbacks.isEmpty()) {
  // Request callbacks
  callbacksBuilder.nameCallback(
    "First Name", "First Name");
  callbacksBuilder.nameCallback(
    "Last Name", "Last Name");
} else {
  // Callbacks returned
  var firstName =
    callbacks.getNameCallbacks().get(0);
  var lastName =
    callbacks.getNameCallbacks().get(1);

  nodeState.putShared("FirstName", firstName);
  nodeState.putShared("LastName", lastName);

  action.goTo("true");
}
javascript

Learn more about next-generation bindings and library scripts in:

In cases where reimplementation isn’t possible, you can request the functionality to be included as a secure script binding in a future release.

Supported libraries

PingOne Advanced Identity Cloud uses the Mozilla Rhino JavaScript engine version 1.7.14 to run JavaScript. Rhino has limited support for ES6 / ES2015 (JavaScript version 1.7).

Learn more in Rhino ES2015 Support.

Thread pools

PingOne Advanced Identity Cloud scripting engines configure security and thread pools.

The scripting engine defines a thread pool for each script type.

Each script executes in an individual thread. The scripting engine allocates threads until it reaches a maximum of 50 threads per pool. When the scripting engine reaches the maximum number of threads, it queues scripts until a thread becomes available.

When a script has either completed or remained idle for more than 60 seconds, the script engine terminates the thread and makes it available to the pool.

Next-generation scripts

The next-generation scripting engine offers the following benefits:

Stability
  • A stable set of enhanced bindings that reduces the need to allowlist Java classes to access common functionality.

Ease of use
  • Simplifying your scripts with fewer imports and more intuitive return types that require less code.

  • Easier debugging through clear log messages and a simple logging interface based on SLF4J.

  • Making requests to other APIs from within scripts is easier with a more intuitive HTTP client.

Reduced complexity
  • Simplify and modularize your scripts with library scripts by reusing commonly used code snippets as CommonJS modules.

    Reference library scripts from a next-generation script.

  • Access identity management information seamlessly through the openidm binding.

Availability

The following script types use the next-generation scripting engine:

These are the only script types that can use library scripts and next-generation bindings.

Migrate to next-generation scripts

To use next-generation bindings, you must migrate eligible legacy scripts.

The next-generation engine can’t use legacy scripts.

Where possible, you should migrate legacy scripts to take advantage of next-generation stability.

You can’t change the script engine version after you have created a script.

To migrate existing scripts, create a new script and convert your legacy code:

  1. Create a script and select Next Generation on the Choose Script Engine page.

  2. Copy and paste the legacy version of your script into the JavaScript field.

  3. Review any Java classes that you needed to allowlist to use in your legacy script.

    You can’t add Java classes to the next-generation allowlist.

    Instead, check if any next-generation bindings provide similar functionality, or reimplement the class as a library script. Library scripts let you add third-party code as reusable JavaScript modules that can be referenced from other scripts.

    If this isn’t possible, you can request the functionality to be included as a supported script binding in a future release.

  4. Migrate the bindings specific to the script type by referring to the relevant API documentation, for example, policy condition scripts or scripted decision node scripts.

  5. Migrate the common bindings by referring to the examples listed in the following table.

    Binding Next-generation change

    Uses native JavaScript objects, similar to the Fetch API.

    Logger is now based on org.slf4j.Logger, instead of com.sun.identity.shared.debug.Debug.

    Use this binding to access the openidm scripting functions supported in IDM.

    Reference secrets and credentials from scripts.

    Use this utility binding to base64 encode/decode strings and to generate random UUIDs and values.

httpClient

Call HTTP services with the httpClient.send method. HTTP client requests are asynchronous, unless the get() method is invoked on the returned object.

You can find examples of sending asynchronous and synchronous requests, sending requests over mTLS and setting timeouts for the clientName instance, and more in Access HTTP services.

Legacy Next-generation
var fr = JavaImporter(
    org.forgerock.openam.auth.node.api.Action);

var requestURL =
    "https://example.com/authenticate";
var request = new
    org.forgerock.http.protocol.Request();

request.setUri(requestURL);                 2
request.setMethod("POST");
request.getHeaders().add("Content-Type",    3
    "application/json;");
request.getHeaders().add("Authorization",
    "Bearer abcd-1234");                    4
request.setEntity(JSON.stringify(
    {"username": "demo"}));

var response =
    httpClient.send(request).get();         5

var responseCode =
    response.getStatus().getCode();         6

if (responseCode === 200) {
    action = fr.Action.goTo("true").build();
} else {
    action = fr.Action.goTo("false").build();
}
javascript
 // import an external library to get token
var authLib = require('authLib');           1
var bearerToken =
    authLib.generateBearer(nodeState);

var options = {                             2
  method: "POST",
  headers: {
    "Content-Type": "application/json"      3
  },
  token: bearerToken,                       4
  body: {
    username: "demo"
  }
}

var requestURL =
    "https://example.com/authenticate";
var response = httpClient.send(
    requestURL, options).get();             5

if (response.status === 200) {              6
    action.goTo("true");
} else {
    action.goTo("false");
}
javascript

1 The example assumes you’ve created a custom library script (authLib) that handles authentication.
2 Set the request options as a native JavaScript object, instead of setting parameters on a Request object.
3 To send a form request, you don’t need to set Content-Type to url-encode parameters. Use the form attribute instead. For details, refer to Access HTTP services.
4 Use Library scripts to reuse common pieces of code; for example, to get an authentication token.
5 Call httpClient.send with the request URL and options as separate arguments, instead of a Request object.
6 Access response data directly using the methods and properties of the returned response object.

logger

The com.sun.identity.shared.debug.Debug logger class is deprecated and replaced by org.forgerock.openam.scripting.logging.ScriptedLoggerWrapper.

ScriptedLoggerWrapper provides a subset of the methods offered by SLF4J.

Learn more in Log script messages.

Legacy Next-generation
var messageEnabled = logger.messageEnabled();
logger.message("Message with arg {}", arg);

var warnEnabled = logger.warningEnabled();
logger.warning("Warn with arg {}", arg);

var errorEnabled = logger.errorEnabled();
logger.error("Error with arg {}", arg);
javascript
var traceEnabled = logger.isTraceEnabled();
logger.trace("Trace with arg {}", arg);

var debugEnabled = logger.isDebugEnabled();
logger.debug("Debug with arg {}", arg);

var infoEnabled = logger.isInfoEnabled();
logger.info("Info with arg {}", arg);

var warnEnabled = logger.isWarnEnabled();
logger.warn("Warn with arg {}", arg);

var errorEnabled = logger.isErrorEnabled();
logger.error("Error with arg {}", arg);
javascript

openidm

The openidm binding lets you manage an IDM resource by calling scripting functions directly from a next-generation script.

The following CRUDPAQ functions are supported:

  • create

  • read

  • update

  • delete

  • patch

  • action

  • query

The following example shows the extensive code required in a legacy script to query the existence of a user by their email address in IDM, compared to the ease of using the openidm binding.

You can find more examples about using the openidm binding in your next-generation scripts in Access IDM scripting functions.

You can find more details about other supported functions in Scripting functions.

The openidm binding provides administrative access to IDM functions. Use it with caution to prevent the exposure of sensitive data.

Legacy Next-generation
function lookupUser (email) {
  try {
    var idmUserEndpoint =
         + '/openidm/managed/alpha_user?
        _queryFilter=userName+eq+%22'
        + email + '%22';
    var request = new
        org.forgerock.http.protocol.Request();
    var accessToken =
        transientState.get("idmAccessToken");     1

    request.setMethod('GET');
    request.setUri(idmUserEndpoint);              1
    request.getHeaders().add('Authorization',
        'Bearer ' + accessToken);
    request.getHeaders().add('Content-Type',
        'application/json');
    request.getHeaders().add('Accept-API-Version',
        'resource=1.0');

    var httpResponse =
        httpClient.send(request).get();           1
    var responseCode =
        httpResponse.getStatus().getCode();
    if (responseCode === 200) {
      var response = JSON.parse(
          httpResponse.getEntity().getString());
      if (response && response.result &&
            response.result.length > 0) {
        // User found
        return {
          success: true,
          user: response.result[0]};
      } else {
        // User NOT found
        return { success: true, user: null };
      }
    } else {
      return {
        success: false,
        error: 'Error looking up user: ' + responseCode
      };
    }
  } catch (e) {
    return {
      success: false,
      error: 'Error querying user: ' + e.toString()
    };
  }
}
javascript
openidm.query("managed/user", {        1
    "_queryFilter":`/userName eq '${email}'`
  }
);
javascript

1 Replace code that gets an idmAccessToken and uses the HTTP client object to invoke a request on an /openidm/* endpoint, with the direct use of the openidm binding.

secrets

The secrets binding is available to all next-generation scripts.

Learn more about the secrets binding in Access secrets and credentials.

Legacy Next-generation
var fr = JavaImporter(
    org.forgerock.openam.auth.node.api.Action);

var pw = secrets.getGenericSecret(          1
           "scripted.node.mysecret").getAsUtf8();

var auth =                                  2
  java.util.Base64.getEncoder().encodeToString(
      java.lang.String().getBytes("demo:" + pw));

nodeState.putTransient("authString", auth);
action = fr.Action.goTo("true").build();
javascript
var pw = secrets.getGenericSecret(          1
           "scripted.node.mysecret").getAsUtf8();

var auth =                                  2
    utils.base64.encode("demo:" + pw);

nodeState.putTransient("authString", auth);
action.goTo("true");
javascript

1 Access the secret from the Advanced Identity Cloud secret store in the same way as the legacy binding.
2 To encode sensitive information, the utils binding replaces the need to allowlist the java.util.Base64 and java.util.Base64$Encoder Java classes.

Exception handling when using next-generation script bindings

You must handle exceptions differently depending on whether the exception occurs within a JavaScript Promise or not.

Both types of exception handling can require that the Java exception class is allowlisted or marked as supported for you to access particular details about the exception. Otherwise, the script can throw an error.

The next-generation scripting engine doesn’t support a configurable allowlist. Learn more in Access Java classes.

General exception handling

When you call a method on a script binding that throws an exception, the scripting engine wraps the exception object in a JavaScript error. You can use this to access the error message in the following way:

try {
  myBinding.myMethod();
} catch (e) {
    // works without requiring support or allowlisting of the exception class
    logger.error(e.message);
}
javascript

If the exception class is allowlisted or the class and method are annotated as @Supported, you can access the underlying Java exception as follows:

try {
  myBinding.myMethod();
} catch (e) {
    // throws an exception if getMyObject() isn't supported or the exception class isn't allowlisted
    myObject = e.javaException.getMyObject();
}
javascript

Exception handling within a Promise

When you handle an exception in a thenCatch block of a Promise, the exception object isn’t wrapped, so it still references the Java exception instead of a JavaScript error.

You can only access the exception object if the exception class is allowlisted or if the fields and methods you want to use are annotated with the @Supported annotation.

For example:

var val = myBinding.methodReturningPromise()
  .then(() => {
    // function to handle the result of the promise
  })
  .thenCatch((e) => {
    // throws a new exception unless the "message" field is supported
    message = e.message;
    // throws an exception unless "getMyObject()" is supported or the exception class is allowlisted
    myObject = e.getMyObject();
    return false;
  }).get();
javascript
As an example, the HttpClientScriptException has a supported message field for logging purposes.

Library scripts

To reuse an existing script, create a library script containing the functionality you want to reuse and reference it from a next-generation script.

A library script can take the format of any JavaScript code. You can also import functionality from another library script.

For example:

  • Create a library script using a minified third-party JavaScript utility library, such as lodash.js.

    Only import scripts from trusted third parties that you know take security seriously. It is your responsibility to ensure that third-party code is secure and to maintain it.

  • Write your own reusable snippet that enhances PingOne Advanced Identity Cloud debugging functionality.

Modules that use file systems, such as node:fs or XMLHTTPRequest, are not supported. Only modules that are self-contained and don’t use a file system explicitly or indirectly are supported.

Create a library script

  1. In the Advanced Identity Cloud admin UI, create a script of type Library.

  2. In the JavaScript editor, paste the contents of a minified third-party JavaScript library or write your own code.

    Expose the reusable functions of your library script by defining properties on the exports object.

    For this example, myExampleLibrary defines and exports three functions:

    function add(i, j) {
      return i + j;
    }
    
    function logTotal(i) {
      logger.info("Total so far: " + i);
    }
    
    // export functions
    exports.add = add;
    exports.logTotal = logTotal;
    
    // export a constant
    exports.PI = 3.14;
    
    // direct export using an inline declaration
    exports.encodeURL = (url) => {
      return utils.base64url.encode(url);
    }
    javascript

    For similar functionality to library scripts, refer to the CommonJS modules.

    You can’t create or export classes in library scripts, only functions and constants.

    As a next-generation script, a library script has access to all the next-generation common bindings. You can also pass in parameters.

    Make sure you don’t use the same name for a local variable as that of a common binding in your script. These names are reserved for common bindings only.

    If you have already defined a local variable with the same name as one that’s added to common bindings in a more recent version of PingOne Advanced Identity Cloud; for example, utils, you must rename the variable in your scripts.

  3. Save your changes.

Import a library script

  1. In the Advanced Identity Cloud admin UI, create or edit a next-generation script.

    Only next-generation scripts support the use of library scripts.

    Alternatively, create or edit a Library script to nest library scripts.

  2. In the JavaScript editor, load the library using the require(LIBRARY_SCRIPT) notation; for example:

    var mylib = require('myExampleLibrary');

  3. Access the exported functions and constants using the library variable; in this case, mylib:

    var i = mylib.add(10, mylib.PI);
    mylib.logTotal(i);
    
    var encoded = mylib.encodeURL("http://maths.example.com");
    javascript
  4. Save your changes.

Scripting API

PingOne Advanced Identity Cloud provides the following scriptable extension points. Each extension point is associated with a script type, or a context.

Configuration provider scripts

Build a configuration map with custom values and add it to the authentication flow.

An authentication journey calls the script through a Configuration Provider node.

For a sample script, refer to config-provider-node.js.

OAuth 2.0 scripts

Extend authorization server behavior with the OAuth 2.0 scripts.

  • Access tokens

    Modify the key-value pairs contained within an OAuth 2.0 access token.

  • Authorize endpoint data provider

    Return additional data from an authorization request.

  • Token exchange

    Add a may_act claim for delegation or impersonation when performing token exchange.

  • OIDC claims

    Populate claims in a request when issuing an ID token or making a request to the OpenID Connect userinfo endpoint.

  • Scope evaluation

    Evaluate and return an OAuth 2.0 access token’s scope information.

  • Scope validation

    Customize the set of requested scopes for authorize, access token, refresh token, and back channel authorize requests.

SAML 2.0 scripts

Customize your SAML 2.0 single sign-on implementation:

  • IdP adapter

    Alter the processing of the authentication request during a SAML 2.0 journey, such as to redirect the user before single sign-on takes place or before a failure response is sent.

  • IdP attribute mapper

    Map user-configured attributes to SAML 2.0 attribute objects to insert into the generated SAML 2.0 assertion.

  • NameID mapper

    Customize the value of the NameID attribute returned in the SAML assertion.

  • SP adapter

    Customize the processing of the authentication request on the SP.

Journey decision node scripts

This extension point lets you write a script to determine the path of an authentication journey. The script provides bindings for accessing data in request headers, shared state, and user session data. This data helps to provide the context for you to decide the possible paths a user could take.

An authentication journey calls the script through a Scripted Decision node.

For more information, refer to the Scripted decision node API.

Library scripts

As part of the next-generation scripting engine, library scripts let you reuse common functionality in Scripted Decision node scripts.

For more information, refer to the Library scripts.

Scripted policy conditions

Use this scriptable extension point to tailor the actions that PingOne Advanced Identity Cloud takes as part of policy evaluation. The script lets you access a user’s profile information, use that information in HTTP calls, and make a policy decision based on the outcome.

An PingOne Advanced Identity Cloud policy calls the script as part of an environment condition. For more information, refer to scripted policy conditions.

For a sample script, refer to policy-condition.js.

Social identity provider profile transformation

Adapt the profile from the provider to align with the profile expected by the platform.

An authentication journey calls the script through a Social Provider Handler node.

For a sample script, refer to normalized-profile-to-managed-user.js.

Script bindings

Each script type exposes a number of bindings, objects that PingOne Advanced Identity Cloud injects into the script execution context. The bindings provide a stable way of accessing PingOne Advanced Identity Cloud functionality, without the need to allowlist Java classes. Scripts are provided with all the bindings for their context at the point of execution.

Find information about context-specific bindings in the documentation for each script type.

PingOne Advanced Identity Cloud has introduced a next-generation scripting engine that offers several benefits, including enhanced script bindings.

The availability and usage of bindings depend on the script engine version of the script: legacy or next-generation. Both versions are described in this section.

You can find information about migrating to the enhanced scripting engine in Migrate to next-generation scripts.

The following bindings are common to many authentication and authorization scripts. Use these bindings to access data and perform script operations such as logging.

Binding

Description

Availability

Legacy

Next-generation

Access the name of the current cookie.

No

Yes

Make outbound HTTP calls, including asynchronous requests and requests using mTLS.

Partial 1

Yes

Write a message to the PingOne Advanced Identity Cloud debug log.

Yes

Yes

Manage an IDM resource.

No

Yes

Access the realm to which the user is authenticating.

Yes

Yes

Access the name of the running script.

Partial 1

Yes

Reference secrets and credentials from scripts.

Partial 2

Yes

Reference system properties.

Yes

Yes

Access utility functions such as base64 encoding/decoding and generating random values and UUIDs.

No

Yes

Manage PingOne Verify transactions related to the user, and manage the associated PingOne user account.

No

Yes

1 Available in OAuth 2.0 script types, scripted decision node scripts, and SAML 2.0 SP scripts.

2 Available in OAuth 2.0 JWT bearer and scripted decision node scripts.

Make sure you don’t use the same name for a local variable as that of a common binding in your script. These names are reserved for common bindings only.

If you have already defined a local variable with the same name as one that’s added to common bindings in a more recent version of PingOne Advanced Identity Cloud; for example, utils, you must rename the variable in your scripts.

Retrieve cookie name

The cookieName binding lets you access the name of the cookie as a string. You can use the cookie name to perform session actions such as ending all open sessions for a user.

// add cookie name to shared state, for example: 8a92ca506c38f08
nodeState.putShared("myCookie", cookieName);
javascript

Access HTTP services

Call HTTP services with the httpClient.send method. HTTP client requests are asynchronous, unless you invoke the get() method on the returned object.

The following examples demonstrate different ways to send HTTP client requests:

Methods

The httpClient binding uses native JavaScript objects, and behaves like the Fetch API.

To invoke an HTTP request:

  • ResponseScriptWrapper httpClient.send(String uri, Map requestOptions).get()

    Sends a synchronous request to the specified URI with request options. The requestOptions parameter is a native JavaScript object that supports method, headers, form, clientName, token, and body as attributes.

    Use the requestOptions attribute, form, to send a form request. The form attribute automatically url-encodes fields, so you don’t need to specify "Content-Type": "application/x-www-form-urlencoded" as part of the headers.

    For example:

    var requestOptions = {
      method: "POST",
      form: {
        field1: "value1",
        field2: "value2"
      }
    }
    javascript
  • ResponseScriptWrapper httpClient.send(String uri).get()

    Sends a synchronous GET request with no additional request options.

To access response data:

  • Map response.formData()

  • Map response.json()

  • String response.text()

    The following fields provide response status information:

    Field Type

    headers

    Map

    ok

    boolean

    status

    integer

    statusText

    String

    The response is similar to Response object behavior.

The httpClient script binding automatically adds the current transaction ID as an HTTP header, X-ForgeRock-TransactionId. This lets you correlate caller and receiver logs when you use httpClient from your script to make requests to other PingOne Advanced Identity Cloud products and services.

Example: Send a synchronous request

The following example uses the httpClient binding to send a synchronous authentication request and check for success.

For an example of how to use httpClient with basic authorization, refer to Access secrets and credentials.

This example assumes you’ve created a custom library script (authLib) that handles authentication.

// import the library script that handles authentication
var authLib = require("authLib");
// use the library function to get authentication token
var bearerToken = authLib.generateBearer(nodeState);

var requestOptions = {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  token: bearerToken, // Equivalent to Authorization header
  body: {
    username: "bjensen"
  }
}

var requestURL = "https://my.auth.server/authenticate";
var response = httpClient.send(requestURL, requestOptions).get();

if (response.status === 200) {
    action.goTo("true");
} else {
    action.goTo("false");
}
javascript

Example: Send an asynchronous request

The httpclient binding also supports asynchronous requests so that you can perform non-blocking operations, such as recording logging output after the script has completed.

To make an asynchronous request, use the same method signatures to send the request but without calling get() on the returned object. The send() method then initiates a separate thread to handle the response. Callers are unable to control when the asynchronous call is processed, so won’t be able to use the response as part of authentication processing.

public Promise<ResponseScriptWrapper, HttpClientScriptException> send(String uri)
public Promise<ResponseScriptWrapper, HttpClientScriptException> send(String uri, Map<String, Object> requestOptions)
javascript

For example:

var requestURL = "https://my.auth.server/audit";
// creates separate thread to handle response
var response = httpClient.send(requestURL).then((response) => {
  if (!response) {
    logger.error("Bad response from " + requestURL);
    return;
  }
  if (response.status != 200) {
    logger.error("Unexpected response: " + response.statusText);
    return;
  }
  logger.debug("Returned from async request");
});
// continues processing whilst awaiting response
action.goTo("true");
javascript

Example: Send a request using mTLS

Configure the httpclient to use mTLS to exchange data securely when making an HTTP request to an external service.

Follow these example steps to send an HTTP request using mTLS:

Configure the HTTP Client service

Complete these steps to configure an instance of the HTTP Client service.

The instance defines settings such as timeout values and the client certificate or truststore secret labels required by the httpclient script binding to make a TLS connection. You can configure the instance to override default values.

For example, to set connection or response timeout values for a request initiated by the HTTP client, enable Use Instance Timeouts in the service instance and set the timeout accordingly.

You can find information about these settings in the Http Client service configuration.

  1. In the AM admin UI (native console), go to Realms > Realm Name > Services.

  2. Click Add a Service and select Http Client Service from the service type drop-down list.

  3. Enable the service and save your changes.

  4. On the Secondary Configurations tab, click Add a Secondary Configuration.

  5. Provide a name for the HTTP client instance; for example, myHttpClient, and click Create.

  6. Enable the instance and save your changes.

  7. On the TLS Configuration tab, enter an identifier to be used in your secret label in the Client Certificate Secret Label Identifier field.

    For example, testCrt creates the dynamic secret label, am.services.httpclient.mtls.clientcert.testCrt.secret.

    To specify a truststore to verify the target server’s certificate, provide a value for Server Trust Certificates Secret Label Identifier.

    This creates the dynamic secret label, am.services.httpclient.mtls.servertrustcerts.identifier.secret.

  8. Save your changes.

Map a base64-encoded PEM certificate to the secret label

To prepare a certificate for TLS connections, it must be:

  • Base64-encoded

  • A file containing both a private and public key

  • Uploaded as an ESV using the API

Complete these steps to generate a key pair and map the secret to the dynamic secret label created in the previous step.

  1. Generate a private key and a public key, as described in Generate an RSA key pair.

    You should now have a .pem file that contains a base64-encoded key pair. PingOne Advanced Identity Cloud shares the public key and uses the private key to sign the request.

  2. Get an access token for the realm.

  3. Specify the access token in a REST API call to create a PEM-encoded ESV secret.

    For example, to create a secret named esv-mtls-cert:

    $ curl \
    --request PUT 'https://<tenant-env-fqdn>/environment/secrets/<esv-mtls-cert>' \
    --header 'Authorization: Bearer <access-token>' \
    --header 'Content-Type: application/json' \
    --header 'Accept-API-Version: protocol=1.0;resource=1.0' \
    --data-raw '{
        "encoding": "pem",
        "useInPlaceholders": false,
        "valueBase64": "<base64-encoded PEM-file>"
    }'
    bash

    You must specify the encoding type as pem for the API to recognize the value as a certificate.

  4. Map the secret against the secret label created when you configured the HTTP Client service, for example:

    Secret Label

    am.services.httpclient.mtls.clientcert.testCrt.secret

    alias

    esv-mtls-cert

    The certificate is now uploaded and mapped to the secret label.

Create a script to send the HTTP request

Write a next-generation decision node script to send a request using the HTTP client instance in the request options.

  1. In your script, specify your HTTP client instance as the value for clientName in requestOptions.

    For example:

     var requestOptions = {
        "clientName": "<myhttpclient>"   (1)
     }
    var res = httpClient.send("https://example.com",
                                        requestOptions).get(); (2)
    action.withHeader(Response code: ${res.status});
    
    if (res.status == 200) {
      action.goTo("true").withDescription(response.text());
    } else {
      action.goTo("false");
    };
    javascript
    1 The clientName attribute must reference an enabled instance of the HTTP Client service.
    2 The HTTP client sends the request to an mTLS endpoint that checks for a certificate.
  2. Create a simple journey that includes the scripted decision node to test your changes.

  3. Verify that the HTTP request is sent successfully.

Log script messages

Write messages to Advanced Identity Cloud debug logs by using the logger object.

Scripts that create debug messages have their own logger which is created after the script has executed at least once.

Logger names use the format: scripts.<context>.<script UUID>.(<script name>); for example, `scripts.OIDC_CLAIMS.36863ffb-40ec-48b9-94b1-9a99f71cc3b5.(OIDC Claims Script).

You can find information about debug logs in Get audit and debug logs.

The ScriptedLoggerWrapper is based on the SLF4J logging framework. You can log messages at the following levels:

  • Trace

  • Debug (default level for development tenant environments)

  • Info

  • Warn (default level for staging and production environments)

  • Error

var traceEnabled = logger.isTraceEnabled();
logger.trace("Trace with arg {}", arg);
var debugEnabled = logger.isDebugEnabled();
logger.debug("Debug with arg {}", arg);
var infoEnabled = logger.isInfoEnabled();
logger.info("Info with arg {}", arg);
var warnEnabled = logger.isWarnEnabled();
logger.warn("Warn with arg {}", arg);
var errorEnabled = logger.isErrorEnabled();
logger.error("Error with arg {}", arg);
javascript

Access IDM scripting functions

The openidm binding lets you manage an IDM resource by calling scripting functions directly from a next-generation script.

The following CRUDPAQ functions are supported:

  • create

  • read

  • update

  • delete

  • patch

  • action

  • query

For more information, refer to Scripting functions.

The openidm binding provides administrative access to IDM functions. Use it with caution to prevent the exposure of sensitive data.

The following example illustrates how to create a user, update their details, send an email, and finally delete the user:

var username = "bjensen";

// CREATE: returns the user identity as a JSON object (wrapped in a MapScriptWrapper)
var newUser = openidm.create("managed/alpha_user", null, {
  "userName": username,
  "mail": "bjensen@example.com",
  "givenName": "Barbara",
  "sn": "Jensen"});

// Access the fields directly, for example: ._id, .sn, .city, .country
var userID = newUser._id;

// READ: returns entire identity as a JSON object
var user = openidm.read("managed/alpha_user/" + userID);

// Debug to output all fields
logger.debug("user: " + JSON.stringify(user));

// UPDATE: replaces entire identity with specified object
// Returns the updated identity as a JSON object
user.description = 'New description';
var updatedUser = openidm.update("managed/alpha_user/" + userID, null, user);

// PATCH: selectively modify object, returns entire identity
var patchedUser = openidm.patch("managed/alpha_user/" + userID, null, [{
        "operation":"replace",
        "field":"/mail",
        "value":"new@example.com"
}]);

// QUERY: returns results array in a map
var queryRes = openidm.query("managed/alpha_user",
    {"_queryFilter":`/userName eq '${username}'`},["*", "_id"]);

// Debug query result count and the requested properties
logger.debug("Query result count: " + queryRes.resultCount);
logger.debug("Queried user: " + queryRes.result[0].givenName);

// ACTION: send email using the action function
var actionRes = openidm.action("external/email", "send", {
    "from": "admin@example.com",
    "to": patchedUser.mail,
    "subject": "Example email",
    "body": "This is an example"
});
// Example response if not null: {"status":"OK","message":"Email sent"}
logger.debug("Status: " + actionRes.status + " : " + actionRes.message);

// DELETE: returns deleted object if successful, throws exception if not
openidm.delete('managed/alpha_user/'+ userID, null);

action.goTo("true");
javascript

Output realm name

The realm binding lets you access the name of the realm to which the user is authenticating as a string.

For example, authenticating to the alpha realm returns a string value of /alpha.

// log current realm
logger.debug("User authentication realm: " + realm);
javascript

Output script name

Use the scriptName binding to get the name of the running script as a string.

// log current script name
logger.debug("Running script: " + scriptName);

// or use a library script to log script name
var mylib = require('loggingLibrary');
mylib.debug(logger, scriptName);
javascript

Access secrets and credentials

Use the secrets binding to access ESVs configured in the realm’s ESV secret store.

For example, a script can access credentials or secrets to make outbound calls to a third-party REST service without hard-coding those credentials in the script.

Only secret labels that begin with the string scripted.node. are accessible to scripts.

Methods

Use the following method to return the value of the specified secret label:

String secrets.getGenericSecret(String secretLabel)

If the secret label is defined at the realm level, its value is returned; otherwise, the script returns the value defined at the global level.

To format the returned secret value, use these supported methods:

  • getAsBytes() Retrieve the secret value in byte[] format.

  • getAsUtf8() Retrieve the secret value in UTF-8 format.

Learn more about the supported methods in the Secret and ScriptedSecrets Javadoc.

Examples

The following example scripts show how to get a password from a secret label named scripted.node.example.secret. The scripts use the encoded username (bjensen) and password (passwd) in a basic authentication header to access the http://httpbin.org/basic-auth/{user}/{passwd} service.

// secret [cGFzc3dk] stored as an ESV
var password = secrets.getGenericSecret("scripted.node.example.secret").getAsUtf8();

var auth = utils.base64.encode("bjensen:" + password);

var requestURL = "http://httpbin.org/basic-auth/bjensen/passwd";

var requestOptions = {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Basic ".concat(auth)
  },
}
var response = httpClient.send(requestURL, requestOptions).get();

if (!response) {
  logger.error("Bad response from " + requestURL);
  action.goTo("false");
} else {
  if (response.status != 200) {
    logger.warn("Authentication not successful. Code: " + response.status);
    action.goTo("false");
  } else {
    logger.debug("Authenticated: " + response.json().authenticated);
    action.goTo("true");
  }
}
javascript

To construct the header for basic authorization, make sure you use the concat() function rather than + to append credentials.

Reference ESVs in scripts

The systemEnv binding, available to all script types, provides the following methods shown with their Java signatures:

String getProperty(String propertyName);
String getProperty(String propertyName, String defaultValue);
<T> T getProperty(String propertyName, String defaultValue, Class<T> returnType);
java

where:

  • propertyName refers to an . For details, refer to ESVs.

    The propertyName always starts with esv.; for example, esv.my.variable.

    Make sure the propertyName is specific enough to distinguish it from all other ESVs defined.

  • defaultValue is a default value to use when no ESV matches propertyName.

    The defaultValue must not be null.

  • returnType is one of the following fully-qualified Java class names:

    • java.lang.Boolean

    • java.lang.Double

    • java.lang.Integer

    • java.lang.String

    • java.util.List

    • java.util.Map

The getProperty(String propertyName) method returns null when the propertyName is not valid.

For example:

var myProperty = systemEnv.getProperty('esv.my.variable');
var myDefault = systemEnv.getProperty('esv.nonexisting.variable', 'defaultValue');
var myDouble = systemEnv.getProperty('esv.double.variable', '0.5', java.lang.Double);
var myBool = systemEnv.getProperty('esv.bool.variable', false, java.lang.Boolean);
var myInt = systemEnv.getProperty('esv.int.variable', 34, java.lang.Integer);
var map = systemEnv.getProperty('esv.map.variable', '{"defaultKey":"defaultValue"}', java.util.Map);
javascript

Access utility functions

Use the next-generation utils binding to base64 encode or decode text and generate random values or UUIDs.

Methods

String crypto.randomUUID()

Returns a type 4 pseudo-random generated UUID.

<JavaScript array> crypto.getRandomValues(<JavaScript array> array)

Returns the specified array filled with the same number of generated random numbers.

String base64.encode(String toEncode)

Encodes the specified text using base64.

String base64.decode(String toDecode)

Decodes the specified text using base64.

String base64url.encode(String toEncode)

Encodes the specified text using base64url.

String base64url.decode(String toDecode)

Decodes the specified text using base64url.

// generate a pseudorandom UUID (version 4)
var uuid = utils.crypto.randomUUID();
logger.debug("UUID: " + uuid); //eef5b4e1-ae86-4c0a-9160-5afee2b5e791

// generate an array of 5 random values
var array = [0,0,0,0,0];
utils.crypto.getRandomValues(array);
array.forEach(function(element)){
  logger.debug("Random value: " + element);
}

// encode a string
var encoded = utils.base64.encode("exampletext")
logger.debug("Encoded text: " + encoded); //ZXhhbXBsZXRleHQ=

var decoded = utils.base64.decode(encoded);
logger.debug("Decoded text: " + decoded);

// encode a URL
var encodedURL = utils.base64url.encode("http://exampletext=")
logger.debug("Encoded URL: " + encodedURL); //aHR0cDovL2V4YW1wbGV0ZXh0PQ

var decodedURL = utils.base64url.decode(encodedURL);
logger.debug("Decoded URL: " + decodedURL);
javascript

Access PingOne Verify transactions and manage associated user

Use the methods in the verifyTransactionsHelper class to obtain information about PingOne Verify transactions the user has performed, and manage the associated user account in PingOne.

Methods

Map getLastVerifyTransaction()

Retrieve the user’s most recent transaction they performed with PingOne Verify.

Returns a map containing the data about the most recent transaction or null if no transactions are available.

View example return data
{
    "_links": {
        "self": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-fak3-49f5-a5d9-80ad5c98f9f6/users/36ff33da-abc7-ch15-abab-8b2412345461/verifyTransactions/dd5a6d4f-m0nd-0819-b107-85a0a10138c6"},
        "environment": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-fak3-49f5-a5d9-80ad5c98f9f6"},
        "user": {
            "href": "https://api.pingone.com/v1/users/36ff33da-abc7-ch15-abab-8b2412345461"}
    },
    "id": "dd5a6d4f-m0nd-0819-b107-85a0a10138c6",
    "transactionStatus": {
        "status": "SUCCESS",
        "verificationStatus": {
            "GOVERNMENT_ID": "SUCCESS",
            "LIVENESS": "SUCCESS",
            "FACIAL_COMPARISON_DOCUMENT": "SUCCESS"
        }
    },
    "verifiedDocuments": "[selfie, liveness, driver_license]",
    "createdAt": "2024-12-09T13:45:34.882Z",
    "updatedAt": "2024-12-09T13:45:34.882Z",
    "expiresAt": "2024-12-09T14:15:34.882Z"
}
json
Map getAllVerifyTransactions()

Retrieve all the user’s transactions performed with PingOne Verify.

Returns a map containing the data about all transactions or null if no transactions are available.

View example return data
{
    "_links": {
        "self": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/36ff33da-cba7-4d46-bedc-8b242889d461/verifyTransactions"
        }
    },
    "_embedded": {
        "verifyTransactions": [
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/36ff33da-cba7-4d46-bedc-8b242889d461/verifyTransactions/029ea878-2618-4067-b7e3-922591e6b55f"
                    },
                    "environment": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
                    },
                    "user": {
                        "href": "https://api.pingone.com/v1/users/36ff33da-cba7-4d46-bedc-8b242889d461"
                    }
                },
                "id": "029ea878-2618-4067-b7e3-922591e6b55f",
                "transactionStatus": {
                    "status": "APPROVED_NO_REQUEST"
                },
                "createdAt": "2022-02-17T20:32:22.052Z",
                "updatedAt": "2022-02-17T20:32:58.711Z",
                "expiresAt": "2022-02-17T21:02:58.711Z"
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/36ff33da-cba7-4d46-bedc-8b242889d461/verifyTransactions/cca479e7-d130-4e3c-b888-74ba1920f59a"
                    },
                    "environment": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
                    },
                    "user": {
                        "href": "https://api.pingone.com/v1/users/36ff33da-cba7-4d46-bedc-8b242889d461"
                    }
                },
                "id": "cca479e7-d130-4e3c-b888-74ba1920f59a",
                "transactionStatus": {
                    "status": "REQUESTED"
                },
                "qrUrl": "https://api.pingone.com/v1/idValidations/shortcode/086645084110/qr",
                "verificationCode": "086645084110",
                "createdAt": "2022-02-17T20:23:58.662Z",
                "updatedAt": "2022-02-17T20:23:58.662Z",
                "expiresAt": "2022-02-17T20:53:58.662Z"
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/36ff33da-cba7-4d46-bedc-8b242889d461/verifyTransactions/52c9bf9a-0687-4e01-85d1-9caa9bb387ee"
                    },
                    "environment": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
                    },
                    "user": {
                        "href": "https://api.pingone.com/v1/users/36ff33da-cba7-4d46-bedc-8b242889d461"
                    }
                },
                "id": "52c9bf9a-0687-4e01-85d1-9caa9bb387ee",
                "transactionStatus": {
                    "status": "REQUESTED"
                },
                "qrUrl": "https://api.pingone.com/v1/idValidations/shortcode/008299320746/qr",
                "verificationCode": "008299320746",
                "createdAt": "2022-02-17T20:23:08.887Z",
                "updatedAt": "2022-02-17T20:23:08.887Z",
                "expiresAt": "2022-02-17T20:53:08.887Z"
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/36ff33da-cba7-4d46-bedc-8b242889d461/verifyTransactions/dd5a6d4f-a999-4819-b107-85a0a10138c6"
                    },
                    "environment": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
                    },
                    "user": {
                        "href": "https://api.pingone.com/v1/users/36ff33da-cba7-4d46-bedc-8b242889d461"
                    }
                },
                "id": "dd5a6d4f-a999-4819-b107-85a0a10138c6",
                "transactionStatus": {
                    "status": "REQUESTED"
                },
                "createdAt": "2021-12-09T13:45:34.882Z",
                "updatedAt": "2021-12-09T13:45:34.882Z",
                "expiresAt": "2022-12-09T14:15:34.882Z"
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/36ff33da-cba7-4d46-bedc-8b242889d461/verifyTransactions/bfc414cb-a1b4-46b8-b622-3d806a85002f"
                    },
                    "environment": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
                    },
                    "user": {
                        "href": "https://api.pingone.com/v1/users/36ff33da-cba7-4d46-bedc-8b242889d461"
                    }
                },
                "id": "bfc414cb-a1b4-46b8-b622-3d806a85002f",
                "transactionStatus": {
                    "status": "REQUESTED"
                },
                "createdAt": "2021-12-08T21:34:52.424Z",
                "updatedAt": "2021-12-08T21:34:52.424Z",
                "expiresAt": "2022-12-08T22:04:52.424Z"
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/36ff33da-cba7-4d46-bedc-8b242889d461/verifyTransactions/b21db4c4-01c5-47b5-a2a9-3d8df21d189b"
                    },
                    "environment": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
                    },
                    "user": {
                        "href": "https://api.pingone.com/v1/users/36ff33da-cba7-4d46-bedc-8b242889d461"
                    }
                },
                "id": "b21db4c4-01c5-47b5-a2a9-3d8df21d189b",
                "transactionStatus": {
                    "status": "APPROVED_NO_REQUEST"
                },
                "createdAt": "2021-12-07T21:33:22.088Z",
                "updatedAt": "2021-12-07T21:45:22.944Z",
                "expiresAt": "2022-12-07T22:15:22.944Z"
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/36ff33da-cba7-4d46-bedc-8b242889d461/verifyTransactions/e44ebfe2-6a76-4ffa-ac35-d71ee9365e57"
                    },
                    "environment": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
                    },
                    "user": {
                        "href": "https://api.pingone.com/v1/users/36ff33da-cba7-4d46-bedc-8b242889d461"
                    }
                },
                "id": "e44ebfe2-6a76-4ffa-ac35-d71ee9365e57",
                "transactionStatus": {
                    "status": "APPROVED_NO_REQUEST"
                },
                "createdAt": "2021-12-07T19:55:16.630Z",
                "updatedAt": "2021-12-07T21:31:26.835Z",
                "expiresAt": "2022-12-07T22:01:26.835Z"
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/36ff33da-cba7-4d46-bedc-8b242889d461/verifyTransactions/fc695b11-93a4-48bb-9ec3-ff5738e3818c"
                    },
                    "environment": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
                    },
                    "user": {
                        "href": "https://api.pingone.com/v1/users/36ff33da-cba7-4d46-bedc-8b242889d461"
                    }
                },
                "id": "fc695b11-93a4-48bb-9ec3-ff5738e3818c",
                "transactionStatus": {
                    "status": "REQUESTED"
                },
                "createdAt": "2021-12-07T18:36:48.156Z",
                "updatedAt": "2021-12-07T18:36:48.156Z",
                "expiresAt": "2021-12-07T19:06:48.153Z"
            }
        ]
    },
    "size": 8
}
json
Map getAllMetadata(String transactionId)

Retrieve the metadata for a specified transaction.

Metadata includes information about the reasons behind a transaction decision, such as scores, probability, and the judgements made, rather than the actual data provided to the transaction by the user.

Returns a map containing the details of the transaction result from the verification services used, or null if the specified transaction is not available.

View example return data
{
    "_links": {
        "self": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/03df72b1-b80b-4449-8eef-ee8f85f48d94/verifyTransactions/7668563d-0226-4ca5-8401-03f6dc5bcdc6/metaData"
        },
        "user": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/03df72b1-b80b-4449-8eef-ee8f85f48d94"
        },
        "environment": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
        },
        "verifyTransaction": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/03df72b1-b80b-4449-8eef-ee8f85f48d94/verifyTransactions/7668563d-0226-4ca5-8401-03f6dc5bcdc6"
        }
    },
    "_embedded": {
        "metaData": [
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/03df72b1-b80b-4449-8eef-ee8f85f48d94/verifyTransactions/7668563d-0226-4ca5-8401-03f6dc5bcdc6/metaData/4ebb9165-4e5c-4270-94e4-d50d7b17ecb4"
                    }
                },
                "id": "4ebb9165-4e5c-4270-94e4-d50d7b17ecb4",
                "provider": "IDRND",
                "type": "LIVENESS",
                "status": "SUCCESS",
                "data": {
                    "score": 6.4909873,
                    "probability": 0.99848527,
                    "quality": 0.94462675
                },
                "retry": {
                    "attempt": 2
                }
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/03df72b1-b80b-4449-8eef-ee8f85f48d94/verifyTransactions/7668563d-0226-4ca5-8401-03f6dc5bcdc6/metaData/ae186d6c-1a79-4912-bad8-79afa3626cca"
                    }
                },
                "id": "ae186d6c-1a79-4912-bad8-79afa3626cca",
                "provider": "IDRND",
                "type": "INJECTION_DETECTION",
                "status": "SUCCESS",
                "data": {
                    "probability": 1.0
                }
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/03df72b1-b80b-4449-8eef-ee8f85f48d94/verifyTransactions/7668563d-0226-4ca5-8401-03f6dc5bcdc6/metaData/546d3a8e-f606-4078-92f1-96a5c2d003e9"
                    }
                },
                "id": "546d3a8e-f606-4078-92f1-96a5c2d003e9",
                "provider": "AMAZON",
                "type": "FACIAL_COMPARISON",
                "status": "SUCCESS",
                "data": {
                    "similarity": 99.37002,
                    "confidence": 99.99767,
                    "quality": {
                        "brightness": 36.77353,
                        "sharpness": 20.92731
                    }
                }
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/03df72b1-b80b-4449-8eef-ee8f85f48d94/verifyTransactions/7668563d-0226-4ca5-8401-03f6dc5bcdc6/metaData/96315a69-fb46-4d28-9b0d-c79927e59df1"
                    }
                },
                "id": "96315a69-fb46-4d28-9b0d-c79927e59df1",
                "provider": "BIOGRAPHIC_MATCHER",
                "type": "BIOGRAPHIC_MATCH",
                "status": "SUCCESS",
                "data": {
                    "biographic_match_results": [
                        {
                            "identifier": "address",
                            "match": "NOT_APPLICABLE"
                        },
                        {
                            "identifier": "given_name",
                            "match": "NONE"
                        },
                        {
                            "identifier": "family_name",
                            "match": "HIGH"
                        },
                        {
                            "identifier": "birth_date",
                            "match": "HIGH"
                        }
                    ]
                }
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/03df72b1-b80b-4449-8eef-ee8f85f48d94/verifyTransactions/7668563d-0226-4ca5-8401-03f6dc5bcdc6/metaData/fba13756-8c24-49ff-9b42-ff1a3661d0ae"
                    }
                },
                "id": "fba13756-8c24-49ff-9b42-ff1a3661d0ae",
                "provider": "MITEK",
                "type": "DOCUMENT_AUTHENTICATION",
                "status": "SUCCESS",
                "data": {
                    "mitekVerifications": [
                        {
                            "name": "Document Ensemble Authenticator",
                            "judgement": "Authentic",
                            "verificationType": 202,
                            "probability": 753,
                            "version": "3.47.0.7114",
                            "documentId": "048f28f1-a7fe-42a5-9722-f10977606719"
                        },
                        {
                            "name": "Black And White Copy",
                            "judgement": "Authentic",
                            "verificationType": 102,
                            "probability": 717,
                            "version": "3.47.0.7114",
                            "documentId": "e290d74d-bf9c-4116-9fe7-9b6fb909c856"
                        },
                        {
                            "name": "Image Classification",
                            "judgement": "Authentic",
                            "verificationType": 105,
                            "probability": 1000,
                            "version": "3.47.0.7114",
                            "documentId": "e290d74d-bf9c-4116-9fe7-9b6fb909c856"
                        },
                        {
                            "name": "Data Comparison",
                            "judgement": "Authentic",
                            "verificationType": 700,
                            "probability": 1000,
                            "version": "3.47.0.7114",
                            "documentId": "e290d74d-bf9c-4116-9fe7-9b6fb909c856"
                        },
                        {
                            "name": "Ensemble Authenticator",
                            "judgement": "Authentic",
                            "verificationType": 201,
                            "probability": 753,
                            "version": "3.47.0.7114",
                            "documentId": "e290d74d-bf9c-4116-9fe7-9b6fb909c856"
                        },
                        {
                            "name": "ID Document Blacklist",
                            "judgement": "Authentic",
                            "verificationType": 101,
                            "probability": 1000,
                            "version": "3.47.0.7114",
                            "documentId": "e290d74d-bf9c-4116-9fe7-9b6fb909c856"
                        },
                        {
                            "name": "Generic Font",
                            "judgement": "Authentic",
                            "verificationType": 104,
                            "probability": 926,
                            "version": "3.47.0.7114",
                            "documentId": "e290d74d-bf9c-4116-9fe7-9b6fb909c856"
                        },
                        {
                            "name": "MRZ Check Digit",
                            "judgement": "Authentic",
                            "verificationType": 601,
                            "probability": 1000,
                            "version": "3.47.0.7114",
                            "documentId": "e290d74d-bf9c-4116-9fe7-9b6fb909c856"
                        },
                        {
                            "name": "MRZ Font Type Authentication",
                            "judgement": "Authentic",
                            "verificationType": 600,
                            "probability": 1000,
                            "version": "3.47.0.7114",
                            "documentId": "e290d74d-bf9c-4116-9fe7-9b6fb909c856"
                        },
                        {
                            "name": "Image Processing",
                            "judgement": "Authentic",
                            "verificationType": 710,
                            "probability": 1000,
                            "version": "1.0",
                            "documentId": "e290d74d-bf9c-4116-9fe7-9b6fb909c856"
                        },
                        {
                            "name": "Document Liveness",
                            "judgement": "Authentic",
                            "verificationType": 108,
                            "probability": 999,
                            "version": "1.0",
                            "documentId": "e290d74d-bf9c-4116-9fe7-9b6fb909c856"
                        }
                    ],
                    "frontImageDocumentId": "e290d74d-bf9c-4116-9fe7-9b6fb909c856",
                    "documentEvidenceId": "048f28f1-a7fe-42a5-9722-f10977606719",
                    "retry": {
                        "attempt": 1
                    }
                }
            }
        ]
    },
    "previousAttempts": [
        {
            "_links": {
                "self": {
                    "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/03df72b1-b80b-4449-8eef-ee8f85f48d94/verifyTransactions/7668563d-0226-4ca5-8401-03f6dc5bcdc6/metaData/06aebfbd-0053-4860-8b59-4f3cb7371dcb"
                }
            },
            "id": "06aebfbd-0053-4860-8b59-4f3cb7371dcb",
            "provider": "IDRND",
            "type": "LIVENESS",
            "status": "FAIL",
            "data": {
                "score": 2.4509223,
                "probability": 0.40062885,
                "quality": 0.40874674
            },
            "retry": {
                "attempt": 1
            }
        }
    ],
    "size": 5
}
json
Map getAllVerifiedData(String transactionId)

For up to 30 minutes after a PingOne Verify decision you can retrieve information about all the data obtained by all the verification service providers during the specified transaction.

Returns a map containing information about the data returned from the verification service used, or null if the specified transaction is not available.

This method only lists the ID and type of verified data available, as the actual data could be large binary files such as images.

Use the getVerifiedData() method to get the actual verified data.

View example return data
{
    "_links": {
        "self": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/"
        },
        "user": {
            "href": "https://api.pingone.com/v1/users/a27dec16-1e80-4f10-a261-2cac46a12b78"
        },
        "environment": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
        },
        "transaction": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b"
        }
    },
    "_embedded": {
        "verifiedData": [
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/84170421-62c6-49a5-b343-496bee93c206"
                    }
                },
                "id": "84170421-62c6-49a5-b343-496bee93c206",
                "type": "GOVERNMENT_ID",
                "createdAt": "2022-02-23T15:51:01.603Z",
                "retry": {
                    "attempt": 2
                }
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/ef6cd8cd-d869-4695-af69-29c78b6f041a"
                    }
                },
                "id": "ef6cd8cd-d869-4695-af69-29c78b6f041a",
                "type": "SELFIE",
                "createdAt": "2022-02-25T16:22:35.649Z",
                "retry": {
                    "attempt": 2
                }
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/bf6cd8cd-d869-4695-af69-29c78b6f041a"
                    }
                },
                "id": "bf6cd8cd-d869-4695-af69-29c78b6f041a",
                "type": "BACK_IMAGE",
                "createdAt": "2022-02-25T16:22:35.649Z",
                "retry": {
                    "attempt": 2
                }
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/af6cd8cd-d869-4695-af69-29c78b6f041a"
                    }
                },
                "id": "af6cd8cd-d869-4695-af69-29c78b6f041a",
                "type": "FRONT_IMAGE",
                "createdAt": "2022-02-25T16:22:35.649Z",
                "retry": {
                    "attempt": 2
                }
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/:id"
                    }
                },
                "id": "yf6cd8cd-d869-4695-af69-29c78b6f041a",
                "type": "CROPPED_PORTRAIT",
                "createdAt": "2022-02-25T16:22:35.649Z"
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/:id"
                    }
                },
                "id": "vf6cd8cd-d869-4695-af69-29c78b6f041a",
                "type": "CROPPED_DOCUMENT",
                "createdAt": "2022-02-25T16:22:35.649Z"
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/:id"
                    }
                },
                "id": "if6cd8cd-d869-4695-af69-29c78b6f041a",
                "type": "CROPPED_SIGNATURE",
                "createdAt": "2022-02-25T16:22:35.649Z"
            }
        ],
        "previousAttempts": [
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/320f341f-050b-4351-9585-5f16dba6667c"
                    }
                },
                "id": "320f341f-050b-4351-9585-5f16dba6667c",
                "type": "GOVERNMENT_ID",
                "createdAt": "2022-02-23T15:51:01.603Z",
                "retry": {
                    "attempt": 1
                }
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/ce0bb6d8-82d5-4ad0-b348-e7fb97edc64f"
                    }
                },
                "id": "ce0bb6d8-82d5-4ad0-b348-e7fb97edc64f",
                "type": "SELFIE",
                "createdAt": "2022-02-25T16:22:35.649Z",
                "retry": {
                    "attempt": 1
                }
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/639116d2-c1ff-44f3-bdba-af7e1e1d0bdd"
                    }
                },
                "id": "639116d2-c1ff-44f3-bdba-af7e1e1d0bdd",
                "type": "BACK_IMAGE",
                "createdAt": "2022-02-25T16:22:35.649Z",
                "retry": {
                    "attempt": 1
                }
            },
            {
                "_links": {
                    "self": {
                        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/92faff31-02c5-43af-b09a-c7eac93c59a4"
                    }
                },
                "id": "92faff31-02c5-43af-b09a-c7eac93c59a4",
                "type": "FRONT_IMAGE",
                "createdAt": "2022-02-25T16:22:35.649Z",
                "retry": {
                    "attempt": 1
                }
            }
        ]
    },
    "size": 7
}
json
Map getVerifiedDataByType(String transactionId, String type)

For up to 30 minutes after a PingOne Verify decision you can retrieve information about a specified type of data obtained by all the verification service providers during the specified transaction.

Use the type parameter to specify which type of personally identifiable information (PII) to retrieve.

Available options are:

  • GOVERNMENT_ID

  • BARCODE

  • FRONT_IMAGE

  • BACK_IMAGE

  • SELFIE

  • CROPPED_SIGNATURE

  • CROPPED_DOCUMENT

  • CROPPED_PORTRAIT

  • VOICE_SAMPLE

  • VOICE_INPUT

  • END_USER_CLIENT

  • BIOGRAPHIC_MATCH

Returns a map containing information about the data returned from the verification service used, or null if the specified transaction is not available.

This method only lists the ID and type of verified data available, as the actual data could be large binary files such as images.

Use the getVerifiedData() method to get the actual verified data.

Map getVerifiedData(String transactionId, String verifiedDataId)

For up to 30 minutes after a PingOne Verify decision you can retrieve the actual data the verification service obtained during the specified transaction.

Returns a map containing verified data returned from a verification service used, or null if the specified transaction or verified data ID are not available.

To obtain the verifiedDataId parameter, use the getAllVerifiedData() or getVerifiedDataByType() methods.

View example return data
{
    "_links":{
        "self":{
            "href":"https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b/verifiedData/34613a50-672c-428f-8db9-c67fe09fc4cc"
        },
        "environment":{
            "href":"https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
        },
        "user":{
            "href":"https://api.pingone.com/v1/users/a27dec16-1e80-4f10-a261-2cac46a12b78"
        },
        "transaction":{
            "href":"https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/a27dec16-1e80-4f10-a261-2cac46a12b78/verifyTransactions/0e2ed48f-6c3a-46c4-bcb5-3a6bd791348b"
        }
    },
    "id":"84170421-62c6-49a5-b343-496bee93c206",
    "type":"GOVERNMENT_ID",
    "createdAt":"2022-02-23T15:51:01.603Z",
    "data":{
        "addressCity":"this city",
        "addressState":"this state",
        "addressZip":"11111",
        "birthDate":"1970-01-01",
        "country":"USA",
        "expirationDate":"1970-01-01",
        "firstName":"given",
        "gender":"",
        "idNumber":"11111",
        "issueDate":"1970-01-01",
        "issuingCountry":"",
        "lastName":"surname",
        "nationality":"",
        "weight":""
    },
    "retry":{
        "attempt":1
    }
}
json
Map getUser()

Retrieve the user’s profile information from PingOne.

Returns a map containing user profile data from PingOne, or null if profile data is not available.

View example return data
{
    "_links": {
        "self": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5"
        },
        "environment": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
        },
        "population": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/populations/706ff5fd-c2bc-4c2d-9037-bfa39112a651"
        },
        "devices": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/devices"
        },
        "roleAssignments": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/roleAssignments"
        },
        "password": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/password"
        },
        "password.reset": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/password"
        },
        "password.set": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/password"
        },
        "password.check": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/password"
        },
        "password.recover": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/password"
        },
        "linkedAccounts": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/linkedAccounts"
        },
        "account.sendVerificationCode": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5"
        }
    },
    "_embedded": {
        "population": {
            "_links": {
                "self": {
                    "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/populations/706ff5fd-c2bc-4c2d-9037-bfa39112a651"
                },
                "environment": {
                    "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
                }
            },
            "id": "706ff5fd-c2bc-4c2d-9037-bfa39112a651"
        }
    },
    "id": "2f9c3699-217e-4acb-9e80-9649311b3eb5",
    "environment": {
        "id": "abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
    },
    "population": {
        "id": "706ff5fd-c2bc-4c2d-9037-bfa39112a651"
    },
    "createdAt": "2020-03-11T18:50:09.074Z",
    "enabled": true,
    "lifecycle": {
        "status": "ACCOUNT_OK"
    },
    "mfaEnabled": false,
    "updatedAt": "2020-03-11T18:50:09.074Z",
    "username": "jamesjoyce"
}
json
Map updateUser(Map body)

Update the user’s profile information in PingOne.

Specify the new data to use in the body parameter.

Returns a map containing the updated user profile data from PingOne.

View example body parameter
{
    "username": "joe@example.com",
    "name": {
        "formatted": "Joe Smith",
        "given": "Joe",
        "middle": "H.",
        "family": "Smith",
        "honorificPrefix": "Dr.",
        "honorificSuffix": "IV"
    },
    "nickname": "Putty",
    "title": "Senior Director",
    "preferredLanguage": "en-gb;q=0.8, en;q=0.7",
    "locale": "en-gb",
    "email": "joe@example.com",
    "primaryPhone": "+1.2225554444",
    "mobilePhone": "+1.4445552222",
    "photo": {
        "href": "https://images.example.com?imgID=1255123412"
    },
    "address": {
        "streetAddress": "123 Main Street",
        "locality": "Springfield",
        "region": "WA",
        "postalCode": "98701",
        "countryCode": "US"
    },
    "accountId": "5",
    "type": "tele",
    "timezone": "America/Los_Angeles"
}
json
Void deleteUser()

Delete the user’s profile information from PingOne.

Throws a ScriptedVerifyTransactionsException if unable to delete the user from PingOne.

View example return data
{
    "_links": {
        "self": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5"
        },
        "environment": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
        },
        "population": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/populations/706ff5fd-c2bc-4c2d-9037-bfa39112a651"
        },
        "devices": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/devices"
        },
        "roleAssignments": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/roleAssignments"
        },
        "password": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/password"
        },
        "password.reset": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/password"
        },
        "password.set": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/password"
        },
        "password.check": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/password"
        },
        "password.recover": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/password"
        },
        "linkedAccounts": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5/linkedAccounts"
        },
        "account.sendVerificationCode": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/users/2f9c3699-217e-4acb-9e80-9649311b3eb5"
        }
    },
    "_embedded": {
        "population": {
            "_links": {
                "self": {
                    "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/populations/706ff5fd-c2bc-4c2d-9037-bfa39112a651"
                },
                "environment": {
                    "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
                }
            },
            "id": "706ff5fd-c2bc-4c2d-9037-bfa39112a651"
        }
    },
    "id": "2f9c3699-217e-4acb-9e80-9649311b3eb5",
    "environment": {
        "id": "abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
    },
    "population": {
        "id": "706ff5fd-c2bc-4c2d-9037-bfa39112a651"
    },
    "createdAt": "2020-03-11T18:50:09.074Z",
    "enabled": true,
    "lifecycle": {
        "status": "ACCOUNT_OK"
    },
    "mfaEnabled": false,
    "updatedAt": "2020-03-11T18:50:09.074Z",
    "username": "jamesjoyce"
}
json

Scripted decision node API

A Scripted Decision node calls server-side JavaScript to set the outcome for the node programmatically and determine the path the authentication journey takes. The script can perform actions before setting the outcome.

Create or edit a journey decision node script directly from within in the journey editor.

When configuring the Scripted Decision node, add the two outcomes true and false in Outcomes. Each outcome appears as a node endpoint that you connect to other nodes in the journey.

The outcome, which can be either a String or a CharSequence type, aren’t limited to true and false. Specify a value for each possible outcome and as many outcomes as required by the journey.

Use the action object to set the outcome.

Script bindings

A Scripted Decision node script has access to some or all the common bindings, predefined objects that PingOne Advanced Identity Cloud injects into the script execution context, depending on the script engine type.

You can find information and examples for these bindings, such as httpClient, logger, openidm, and utils, in Script bindings.

In addition to the common bindings, a Scripted Decision node script has access to the following specific bindings.

PingOne Advanced Identity Cloud has introduced a next-generation scripting engine that offers several benefits, including enhanced script bindings.

The availability and usage of bindings depend on the script engine version of the script: legacy or next-generation. Both versions are described in this section.

You can find information about migrating to the enhanced scripting engine in Migrate to next-generation scripts.

Binding Description Further information

action (1)

Set the script outcome and perform script-related actions.

auditEntryDetail

Add information to the PingOne Advanced Identity Cloud audit logs.

callbacksBuilder(1) callbacks

Request additional data from the user using a callback.

existingSession

If the user has previously authenticated and has an authenticated session, use this binding to access the properties of that session.

idRepository

Access the data stored in the user’s profile.

jwtAssertion(1)

Generate JWT assertions in scripts.

jwtValidator(1)

Validate JWT assertions in scripts.

nodeState

Access data set by previous nodes in the journey or store data to be used by subsequent nodes.

requestCookies

Access the request cookies in the login request.

requestHeaders

Access the HTTP headers provided in the login request.

requestParameters

Access the HTTP request parameters provided in the login request.

resumedFromSuspend

Boolean to indicate whether evaluation has resumed after suspension.

For example, the value returns True after sending an email and receiving the response.

samlApplication(1)

Access information about the SAML 2.0 request if the scripted decision node is part of a federated journey associated with an application.

secrets

Access the secrets configured for PingOne Advanced Identity Cloud in the environment.

(1) Not available in Legacy scripts.

Set script outcome and script attributes

Use the action object to define the outcome and access functions to set script attributes, such as error or lockout messages, verified identities, or session properties.

Use action instead of outcome to define the exit path from the node. The action object takes precedence and overrides the value set for the outcome variable. You can also use it to perform other script operations.

Call the goTo method directly on the action binding. Optionally, chain with ActionWrapper functions.

Supported ActionWrapper functions:
Method Information

public ActionWrapper putSessionProperty(String key, String value)

Add a session property.

public ActionWrapper removeSessionProperty(String key)

Remove an existing session property.

public ActionWrapper withDescription(String description)

Set a description for this action.

public ActionWrapper withErrorMessage(String message)

Set an error message to display to the end user when the journey reaches the Failure node.

public ActionWrapper withHeader(String header)

Set a header for this action.

public ActionWrapper withIdentifiedAgent(String agentName)

public ActionWrapper withIdentifiedUser(String username)

Set the identity, authenticated or not, of the user or agent verified to exist in an identity store.

Use these methods to record the type of identified user.

public ActionWrapper withLockoutMessage(String message)

Set an error message to display to the end user when the account is locked or inactive.

public ActionWrapper withStage(String stage)

Set a stage name to return to the client to aid the rendering of the UI.

// Evaluation continues along the "false" outcome
action.goTo("false").withErrorMessage(error);

 // No effect
outcome = "true";
javascript

A script can also use the action object to set session properties and callbacks.

Access request cookies

Use the requestCookies binding to access the cookies in the login request without having to perform string manipulation of the request header.

The requestCookies binding stores the cookie names and values in the format Map<String, String>.

if (typeof requestCookies === "undefined") {
  action.goTo("false");
}

if (Object.keys(requestCookies).length === 0) {
  action.goTo("false");
}

if (requestCookies.containsKey("amlbcookie")) {
  logger.debug("amlbcookie: " + requestCookies.amlbcookie);
}

action.goTo("true");
javascript

Access request header

A script can access the headers in the login request with the methods of the requestHeaders object.

The script has access to a copy of the headers. Changing their values does not affect the request.

String[] requestHeaders.get(String headerName)

Returns an array of the values in the named request header, or null, if the property is not set.

Header names are case-sensitive.

if (requestHeaders.get("user-agent").get(0).indexOf("Chrome") !== -1) {
    action.goTo("true");
} else {
    action.goTo("false");
}
javascript

Access request parameters

A script can access the query parameters in the login request with the methods of the requestParameters object.

The script has access to a copy of the parameters. Changing their values doesn’t affect the request.

String[] requestParameters.get(String parameterName)

Return an array of the values in the named request parameter, or null, if the parameter is not available.

var service;
var authIndexType = requestParameters.get("authIndexType");

if (authIndexType && String(authIndexType.get(0)) === "service") {
    service = requestParameters.get("authIndexValue").get(0);
}

if (service) {
    nodeState.putShared("service", service);
}
action.goTo("true");
javascript

In JavaScript, the data type (typeof) of requestParameters values is object.

Convert the values to strings before using strict equality comparisons.

Access shared state data

A script can access the shared state of the journey with the methods of the nodeState object.

The sharedState and transientState bindings are deprecated, and are removed in the next-generation scripting engine.

You can still access the different states with the nodeState object, which replaces the sharedState and transientState bindings.

For example, use nodeState.putShared() instead of sharedState.put(), and nodeState.putTransient() instead of transientState.put(). Use nodeState.get() to retrieve shared and transient state properties.

There are three types of state:

Shared

Non-sensitive state.

Transient

Sensitive state.

Transient state data is never sent back to the user’s browser in a callback so doesn’t need to be encrypted. The transient state is for sensitive data and for data not required after the next callback.

Secure

Encrypted sensitive state.

Secure state data is sent back to the user’s browser encrypted as part of the shared state object.

Transient state data is promoted to secure state data when:

  • A callback to the user is about to occur.

  • A downstream node is detected in the journey, requesting data in the transient state as script input.

Unless the downstream node explicitly requests the secure state data by name, the authentication journey removes it from the node state after processing the next callback.

For example, a node in a registration journey stores a user’s password in transient state. The node sends a callback to the user before an inner tree node, downstream in the journey, consumes that password. As part of the callback, the journey assesses what to add to the secure state. It does this by checking the state inputs that downstream nodes in the journey require. Nodes that only request * are ignored, as this would result in putting everything that’s in transient state into secure state, and retaining sensitive information longer than necessary.

If a downstream node requires the password, it must therefore explicitly request it as state input, even if it lists the * wildcard as input.

<returnvalue> nodeState.get(String propertyName)

Returns the value of the named property.

The value may come from the transient, secure, or shared states, in that order. If the same property is available in several states, the method returns the value of the property in the transient state first.

If the property isn’t set, the method returns null.

Returns the value as an Object. The JavaScript return type is coerced, so you no longer need to call functions such as asString() or asMap().

var currentAuthLevel = nodeState.get("authLevel");
var thePassword = nodeState.get("password");

action.goTo("true");
javascript
<returnvalue> nodeState.getObject(String propertyName)

Returns the value of the named property.

If the value is a map, this method combines and returns the values stored in transient, secure, and shared state.

If the property isn’t set, the method returns null.

The getObject method returns an immutable object. If you want to update the object, use nodeState.get(String propertyName) instead. The get method doesn’t combine values but returns the first value found in transient, secure, or shared state (in that order). You can then modify the returned object.

For example:

var attributes = nodeState.get("objectAttributes");
if (attributes) {
  attributes.put("mail", "test@example.com");
  nodeState.putShared("objectAttributes", attributes);
}

action.goTo("true");
javascript

Returns the value as an Object. The JavaScript return type is coerced.

var attributes = nodeState.getObject("objectAttributes");

action.goTo("true");
javascript
<returnvalue> nodeState.putShared(String propertyName, String propertyValue)

Sets the value of the named shared state property.

Returns a modified instance of NodeStateScriptWrapper.

try {
  var thePassword = nodeState.get("password");
} catch (e) {
  nodeState.putShared("errorMessage", e.toString());
}

action.goTo("true");
javascript
<returnvalue> nodeState.putTransient(String propertyName, String propertyValue)

Sets the value of the named transient state property.

Returns a modified instance of NodeStateScriptWrapper.

nodeState.putTransient("sensitiveKey", "sensitiveValue");

action.goTo("true");
javascript
<returnvalue> nodeState.mergeShared(Map<String, Object> object)

Adds the specified object to shared state by merging the keys. If a key already exists, in any state, its value is replaced by the specified value.

You can only merge a nested object if it’s a registered state object, such as objectAttributes. If you provide new values for objectAttributes, they’re merged with the existing values. For example, if the following objectAttributes values already exist in secure state:

{ "key1": "a", "key2": "b" }
bash

and you call mergeShared() with this set of values:

{ "key1": "z", "key3": "c"}
bash

then the merged result contains these values when you call getObject("objectAttributes"):

"objectAttributes": { "key1": "z", "key2": "b", "key3": "c"}
bash

where key1 is removed from secure and added to shared state along with key3, and key2 remains in secure state.

Returns a modified instance of NodeStateScriptWrapper.

// specify new values for objectAttributes as a JSON object
nodeState.mergeShared({
  "objectAttributes": {
	"mail": "bjensen@example.com",
    "userName": "bjensen"
  }
})

action.goTo("true");
javascript
<returnvalue> nodeState.mergeTransient(Map<String, Object> object)

Adds the specified object to transient state by merging the keys. If a key already exists, in any state, its value is replaced by the specified value.

You can only merge a nested object if it’s a registered state object, such as objectAttributes. If you provide new values for objectAttributes, they’re merged with the existing values. For example, if the following objectAttributes values already exist in secure state:

{ "key1": "a", "key2": "b" }
bash

and you call mergeTransient() with this set of values:

{ "key1": "z", "key3": "c"}
bash

then the merged result contains these values when you call getObject("objectAttributes"):

"objectAttributes": { "key1": "z", "key2": "b", "key3": "c"}
bash

where key1 is removed from secure and added to transient state along with key3, and key2 remains in secure state.

Returns a modified instance of NodeStateScriptWrapper.

// specify new values for objectAttributes as a JSON object
nodeState.mergeTransient({
  "objectAttributes": {
	"mail": "bjensen@example.com",
    "userName": "bjensen"
  }
})

action.goTo("true");
javascript

Access profile data

A script can access profile data through the methods of the idRepository object.

In these examples, an Identity Store Decision node with Username as Universal Identifier enabled precedes the Scripted Decision node that uses the script. As a result, when you call nodeState.get('username') the function returns the user’s _id, which is required to get the identity object.

Get attribute values

Returns the values of the named attribute for the named user.

// Get UUID by enabling `Username as Universal Identifier`
// in a preceding Identity Store Decision node
var uuid = nodeState.get("username");
var attribute = "mail";

// Get identity object separately
var identity = idRepository.getIdentity(uuid);

// Returns all values as an array, for example: ["test@example.com", "user@example.com"]
identity.getAttributeValues(attribute);

// Returns the first value, for example:  test@example.com
identity.getAttributeValues(attribute)[0];

// If no attribute by this name is found, the result is an empty array: []
identity.getAttributeValues("non-existent-attribute");

action.goTo("true");
javascript
Set attribute values

Sets the named attribute as specified by the attribute value for the named user, and persists the result in the user’s profile.

// Get UUID by enabling `Username as Universal Identifier`
// in a preceding Identity Store Decision node
var uuid = nodeState.get("username");

// Get identity separately
var identity = idRepository.getIdentity(uuid);

// Set the attribute directly on the identity object
identity.setAttribute("mail", ["test@example.com"]);

try {
  // Explicitly persist data
  // throws an exception if setAttribute failed
  identity.store();
  action.goTo("true");
} catch(e) {
  logger.error("Unable to persist attribute. " + e);
  action.goTo("false");
}
javascript

You must explicitly call store() to persist changes to attribute values.

Add attribute values

Add an attribute value to the list of attribute values associated with the attribute name for a particular user.

// Get UUID by enabling `Username as Universal Identifier`
// in a preceding Identity Store Decision node
var uuid = nodeState.get("username");

// Get identity separately
var identity = idRepository.getIdentity(uuid);

// Add a value as a string.
identity.addAttribute("mail", "user@example.com");

try {
  // Explicitly persist data
  // throws an exception if addAttribute failed
  identity.store();
  action.goTo("true");
} catch(e) {
  logger.error("Unable to persist attribute. " + e);
  action.goTo("false");
}
javascript

You must explicitly call store() to persist changes to attribute values.

Set session properties

A script can set session properties with the fields and methods of the Action interface.

The following example sets the outcome to true, and adds a custom session property:

 

action.goTo("true").putSessionProperty("mySessionProperty", "myPropertyValue");
javascript

When adding a session property, make sure it’s in the PingOne Advanced Identity Cloud allowlist. PingOne Advanced Identity Cloud does not allow the script to add it to sessions unless it is in the allowlist.

  1. In AM admin UI, under Realms > Realm Name > Services, make sure the Session Property Whitelist Service is configured.

    If not, configure the service with the default settings.

  2. Add the property name to the Allowlisted Session Property Names list unless it is already present.

Add the script to a Scripted Decision node in your authentication journey. When a user authenticates successfully, PingOne Advanced Identity Cloud adds the property to their session, as shown in the following output for session introspection:

{
  "username": "014c54bd-6078-4639-8316-8ce0e7746fa4",
  "universalId": "id=014c54bd-6078-4639-8316-8ce0e7746fa4,ou=user,o=alpha,ou=services,ou=am-config",
  "realm": "/alpha",
  "latestAccessTime": "2022-10-31T11:03:25Z",
  "maxIdleExpirationTime": "2022-10-31T11:33:25Z",
  "maxSessionExpirationTime": "2022-10-31T13:03:24Z",
  "properties": {
    "AMCtxId": "de5abe95-db97-4354-9d32-aab660ea23a3-4252446",
    "mySessionProperty": "myPropertyValue"
  }
}
json

Query SAML application and authentication request

The samlApplication binding is only present when a journey runs in a SAML 2.0 context and an application journey is configured. You can access the binding object for the duration of the authentication session (determined by the Max duration setting.

Use the samlApplication binding to query the SAML 2.0 authentication request properties and identity provider (IDP) and service provider (SP) configuration attributes.

You can check and debug the configuration values, but changes to properties are confined to the scope of the script and won’t affect the underlying objects.

String getFlowInitiator()

Returns IDP or SP depending on which provider initiates the SAML 2.0 flow.

Map<String, Object> getAuthnRequest()

Returns an object containing the properties of the SAML 2.0 authentication request.

Example authnRequest object

The following example is formatted to display the object structure:

{
  "destination": "https://<tenant-env-fqdn>/am/SSORedirect/metaAlias/idp1",
  "signature": null,
  "subject": null,
  "issueInstant": 1724341924000,
  "consent": "",
  "forceAuthn": false,
  "protocolBinding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
  "mutable": false,
  "issuer": {
    "value": "sp1",
    "nameQualifier": "",
    "format": "",
    "mutable": false,
    "spnameQualifier": "",
    "spprovidedID": ""
  },
  "assertionConsumerServiceURL": "https://<tenant-env-sp-fqdn>/am/Consumer/metaAlias/sp1",
  "@class": "com.sun.identity.saml2.protocol.impl.AuthnRequestImpl",
  "extensions": null,
  "passive": false,
  "version": "2.0",
  "requestedAuthnContext": {
    "@class": "com.sun.identity.saml2.protocol.impl.RequestedAuthnContextImpl",
    "elementName": "RequestedAuthnContext",
    "mutable": false,
    "authnContextClassRef": [
      "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
    ],
    "authnContextDeclRef": [],
    "comparison": "exact"
  },
  "nameIDPolicy": {
    "@class": "com.sun.identity.saml2.protocol.impl.NameIDPolicyImpl",
    "format": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
    "allowCreate": true,
    "mutable": false,
    "spnameQualifier": "sp1"
  },
  "attributeConsumingServiceIndex": null,
  "conditions": null,
  "scoping": null,
  "signed": false,
  "id": "s2c72252b5b2ce2b43e5150c8ee8aba0401f3ef390",
  "providerName": "",
  "assertionConsumerServiceIndex": null
}
json
Map<String, List<String>> getIdpAttributes()

Returns a map containing the extended configuration for the hosted IDP.

Example IDP attributes
IDP attribute Example value

assertionEffectiveTime

[ "600" ]

idpAuthncontextClassrefMapping

[ "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport|0||default" ]

assertionNotBeforeTimeSkew

[ "600" ]

metaAlias

[ "/idp1" ]

idpECPSessionMapper

[ "com.sun.identity.saml2.plugins.DefaultIDPECPSessionMapper" ]

idpAccountMapper

[ "com.sun.identity.saml2.plugins.DefaultIDPAccountMapper" ]

nameIDFormatMap

[ "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress|mail" ]

idpAttributeMapper

[ "com.sun.identity.saml2.plugins.DefaultIDPAttributeMapper" ]

saeIDPUrl

[ "https://<tenant-env-fqdn>/am/idpsaehandler/metaAlias/idp1" ]]

idpAuthncontextMapper

[ "com.sun.identity.saml2.plugins.DefaultIDPAuthnContextMapper" ]

cotlist

[ "cot1" ]

Map<String, List<String>> getSpAttributes()

Returns a map containing the extended configuration for the remote SP.

Example SP attributes
SP attribute Example value

metaAlias

[ ]

treeName

[ "test" ]

idpProxyCount

[ "0" ]

cotlist

[ "cot1" ]

Example

The following script calls the samlApplication methods to log the authentication request and configuration values:

function logObject(objType, obj) {
  var logMsg = objType + " \n";
    for (attr in obj) {
      logMsg += "\t" + attr + " = " + obj[attr] + "\n";
  }
  logger.info(logMsg);
}

if (typeof (samlApplication) === "undefined") {
  logger.error("Journey is not associated with a SAML application");
  action.goTo("false");
}
else {
    var flowType = samlApplication.getFlowInitiator();
    logger.info("Flow type: " + flowType);

    if (flowType == 'SP') {
      var authnRequest = samlApplication.getAuthnRequest();
      logObject("authnRequest", authnRequest);
    }

    logObject("idpAttributes", samlApplication.getIdpAttributes());
    logObject("spAttributes", samlApplication.getSpAttributes());

    action.goTo("true");
}
javascript

Existing session properties

The existingSession binding is only present when performing a session upgrade. Any properties added by nodes earlier in the journey only appear in the new authenticated session when the journey completes. These properties aren’t available in the existingSession binding.

A script can access existing session properties during a session upgrade request with the existingSession.get method.

String existingSession.get(String propertyName)

Returns the string value of the named existing session property, or null, if the property is not set.

If the current request isn’t a session upgrade and doesn’t provide an existing authenticated session, the existingSession variable isn’t declared. Check for a declaration before attempting to access the variable.

if (typeof existingSession !== 'undefined') {
    var existingAuthLevel = existingSession.get("AuthLevel")
} else {
    nodeState.putShared("errorMessage", "Variable existingSession not declared - not a session upgrade.")
}

action.goTo("true");
javascript

Customize account lockout message

A script can display a custom error message to the end user when their account is locked or inactive. To do this, use a script with the .withLockoutMessage method on the Action.goto() object.

The following example changes the account lockout message to a custom error message:

action.goTo("true")
  .withLockoutMessage("Custom lockout message");
javascript

Add the script to a Scripted Decision node in your authentication journey.

This Scripted Decision node must go after a node that checks the status of the user (such as the Identity Store Decision node or the Account Active Decision node) because it is triggered when a user’s account is locked or inactive.

Generate and validate JWTs

Use the jwtAssertion and jwtValidator bindings to include JWT operations in your script.

The jwtAssertion binding lets you generate a signed and/or encrypted JWT, for example, for securely transporting JWT claims that can be verified later in the journey via an emailed magic link. JWTs are signed using the HS256 or RS256 algorithms.

The jwtValidator binding lets you decrypt and validate JWTs; for example, to verify JWT claims from another system such as PingGateway.

String jwtAssertion.generateJwt(Map<String, Object> jwtData)

Provide the following data for generating a JWT assertion:

Data field Description

jwtType

(Required)

The type of JWT to generate, one of:

  • SIGNED

  • SIGNED_THEN_ENCRYPTED

  • ENCRYPTED_THEN_SIGNED

Encrypted options are only supported for HS256 JWTs.

jwsAlgorithm

(Required) HS256 or RS256 (jwtType must be SIGNED).

issuer

The JWT issuer (iss) claim. You must provide a value for either issuer or stableId at a minimum.

audience

The JWT audience (aud) claim.

subject

The JWT subject (sub) claim.

type

The JWT type (typ) claim, typically JWT.

jwtId

Specify a value for the JWT ID (jti).

claims

Custom claims in the format "key":"value".

stableId

Stable ID. You must provide a value for either issuer or stableId at a minimum.

accountId

Deprecated. To support backwards compatibility, the account ID (if specified) is set as the issuer, stableId and subject when these values aren’t provided.

validityMinutes

The duration of the validity of the JWT in minutes.

privateKey

The RSA private key in JSON Web Key (JWK) format. For example, generate your own private key with https://www.openssl.org.

Required for RS256-signed JWTs.

signingKey

The base64-encoded JSON Web Key used to sign/verify the JWT.

Required for HS256-signed JWTS.

encryptionKey

The JSON Web Encryption (JWE) public key used to encrypt/decrypt the JWT. JWTs are encrypted using the A128CBC-HS256 algorithm.

Generation of RS256-encrypted signed JWT assertions isn’t currently supported.

JWT generation example 1: HS256 encrypted then signed:

var hmacJwtData = {
  jwtType: "ENCRYPTED_THEN_SIGNED",
  jwsAlgorithm: "HS256",
  issuer: "issuer",
  subject: "subject",
  audience: "https://am.localtest.me:8080/am/oauth2/access_token",
  type: "JWT",
  validityMinutes: 10,
  claims: {
    "key1": "value1",
    "key2": "value2"
  },
  signingKey: "cGFzc3dvcmQ=",
  encryptionKey: "Syz1K5XQCZtq7FkE+GNvgZPeFyvUXJdemIW7CQjM18U="
};
// returns a string representation of the JWT
var assertionJwt = jwtAssertion.generateJwt(hmacJwtData);

if (assertionJwt !== null && assertionJwt.length > 0) {
  logger.debug("AssertionJwt: " + assertionJwt);
  // store JWT in nodeState for validation later in journey
  nodeState.putShared("assertionJwt" , assertionJwt);
  action.goTo("true");
} else {
  action.goTo("false");
}
javascript

JWT generation example 2: RS256 signed:

var rsaJwtData = {
  jwtType: "SIGNED",
  jwsAlgorithm: "RS256",
  accountId: "accountId",
  audience: "https://am.localtest.me:8080/am/oauth2/access_token",
  validityMinutes: 10,
  privateKey: {
    "p": "0BRJ6TrTpeT3XM1aXj9ZiTDfVTD0Aqufhm0a2Cm7Zr3ObqkBbZrm2KH9BY23nfY_TFbd6kx31YHqjpoV4KeThn2uvZ7gPw_ILljk5WQwgFq_gdDvAq3Iw4MlwGoR51nSaGGqKU6Dt2PvVOB4I3azVJr1f9Vsm47L6Llp8YjKtBE",
    "kty": "RSA",
    "q": "ugtiWmODuy3XuWGf3u5hvF4RglZZK5IPYRkJjSz8j90DxYVPD_CBNJx8j_FGUJ6BBNRTs2yylgMJdcFs-WxcFR7iQD2OzgzSdeRgrh34RmLjAXEq-X0OF9P3lqnBXIx-uVL3-rG8jQWSzc016o3PWjclOKqLx7oBmYs7w6WbJLE",
    "d": "c2DR3SOQkzu6f5eRLFLURphQCbrN4JCAlOo2S_D07UJmMiYtIFpOezbLazQYXdebiV-pPv-zZcOWHhOr3HMgYLu8JBN29mrJS8kDIWMjCx8vMJgrNLfBZO4az--t_Kyow4p0n3HdaYRu0K4lqskXe68Syl0XAlHfnd7q-bB-UUGB89j3E-FfXIIjKktn_koBc8hX2DhUnJgduFi9CcAQVO9wSjjfyB6ksn01_YaMt5MHsHKhqDvZeCdEHfwyFer8vvDRCTru_msl_fu_MqQi15igTJu6f7eBsVQQLnb7L1fQ0BURck7oVMjTvyYz_nRbnMkKSrWpa-j1d1Z4TTucAQ",
    "e": "AQAB",
    "use": "sig",
    "kid": "iUE-em7bU5j1yJN6hEMKx6ZmtWBYGZycCuO94X1sssE",
    "qi": "rUqLFjJ2L3FNrj65tdUCusQ-_7g1rKsTOGnQUrVcgYSsHb3aYR90zV651MiL0X4gp6mzgc8QcSdzc1KbEmR5VHm5IH4N6f9yBNb2yO_8sftmS8PiRjZjVLUORnwmouJ4cPsob0RPx9mwGLIURLxQDstE4UQ0j6iMWF74iezwxO8",
    "dp": "f60DURXkijV9RrdGjPAIK3MOhoJ8JytRvjUyNJMex0MN6L7Q_oT-wsxaqc60bTuMyXW_wyVanmqSFyAa7ndEwVBbKUTUSj2P0kh_YvXgANIuEiS2k4k42CafwnoTNEbcIWpT8_aWQbATSZxWe0Q5c1-F5gN6GdU77zfd9vO9lVE",
    "alg": "RS256",
    "dq": "A8RdPnVLYovgFVnbsdjj07uX4Sq8bXxsoUuvfNNPXd5cyDAV1L3K7_THNObuxI0hEab29ugZiZ4QH_lFqps-FhNlA2X7sUJjNI3mQ0BKGarA6_ONqjWVBnh9R-iyCJyzqC785G-a4MQfH9mq4M_0ReBd-ZLCd83VYHWIRULiLFE",
    "n": "lzf-g94TfT9JeQ7kRZCkSOKwP0rtpe2IzX6C42NN1EE4hejwCjIAj4oUHrcNyrcVZ6hIu3a5r09fnzp5w9RS6ooMkp-AQ_9C5T8hFbf61J4lT3kmIp-jv50MF7oJCGOidLPCiJ-3EsnBKFjomRcCQa_r-sHF9hfORvhypBcj_U1OMMRM28yX7JVggG7f_YPlmx03c7meKPE-4xXnfdFDxbjbntlWSul6qm0YOTRVfuZHubwu-HOZEDB4dKkA3-0qAev6FULW7tNOqoMj5KLlejeiuCOQeAMTqPnBqOdH8iC4u6WHArsRqkmOdLgVs0uLlVPz9796qIKlosP5EMPjwQ"
  }
};

var assertionJwt = jwtAssertion.generateJwt(rsaJwtData);

if (assertionJwt !== null && assertionJwt.length > 0) {
  logger.debug("AssertionJwt: " + assertionJwt);
  // store JWT in nodeState for validation later in journey
  nodeState.putShared("assertionJwt" , assertionJwt);
  action.goTo("true");
} else {
  action.goTo("false");
}
javascript
Map<String, Object> jwtValidator.validateJwtClaims(Map<String, Object> jwtData)

Provide values for the following supported fields to validate the specified JWT assertion:

Data field Description

jwtType

(Required) The type of JWT to validate: SIGNED, SIGNED_THEN_ENCRYPTED, or ENCRYPTED_THEN_SIGNED.

jwt

(Required) The JWT object to validate.

issuer

The JWT issuer (iss) claim. You must provide a value for either issuer or stableId at a minimum.

audience

The JWT audience (aud) claim value.

subject

The JWT subject (sub) claim.

type

The JWT type (typ) claim.

claims

Custom claims in the format "key":"value".

stableId

Stable ID. You must provide a value for either issuer or stableId at a minimum.

accountId

Deprecated. To support backwards compatibility, the account ID (if specified) is used to validate the issuer, stableId and subject when these values aren’t provided.

signingKey

The base64-encoded JSON Web Key used to sign/verify the JWT.

JWTS are signed using the HS256 or RS256 algorithms.

encryptionKey

The JSON Web Encryption (JWE) public key used to encrypt/decrypt the JWT.

JWTs are encrypted using the A128CBC-HS256 algorithm.

Validation of RS256-encrypted signed JWT assertions isn’t currently supported.

JWT validation example 1: HS256 encrypted then signed:

var jwtData = {
  jwtType: "ENCRYPTED_THEN_SIGNED",
  issuer: "issuer",
  subject: "subject",
  audience: "https://am.localtest.me:8080/am/oauth2/access_token",
  type: "JWT",
  signingKey: "cGFzc3dvcmQ=",
  encryptionKey: "Syz1K5XQCZtq7FkE+GNvgZPeFyvUXJdemIW7CQjM18U="
};
// get JWT from nodeState (JWT generation example 1)
var assertionJwt = nodeState.get("assertionJwt");

if (assertionJwt !== null && assertionJwt.length > 0) {
  jwtData["jwt"] = assertionJwt;

  try {
    // returns a map of JWT claims or null if required claims are missing
    // throws NoSuchSecretException if verification key is missing
    var jwtClaims = jwtValidator.validateJwtClaims(jwtData);
    if (jwtClaims !== null) {
      // retrieve and log some JWT claim values
      logger.debug("Audience: " + jwtClaims.get("audience"));
      logger.debug("Subject: " + jwtClaims.get("subject"));
      logger.debug("Expiration Time: " + jwtClaims.get("expirationTime"));
      logger.debug("Issued At: " + jwtClaims.get("issuedAt"));
      logger.debug("JWT ID: " + jwtClaims.get("jwtId"));
      logger.debug("key1: " + jwtClaims.get("key1")); // custom claim
      action.goTo("true");
    } else {
      logger.error("Invalid JWT claims");
      action.goTo("false");
    }
  } catch(e) {
    logger.error("Invalid JWT signing key");
    action.goTo("false");
  }
} else {
  logger.error("Error getting assertionJwt");
  action.goTo("false");
}
javascript

JWT validation example 2: RS256 signed:

var jwtData = {
  jwtType: "SIGNED",
  accountId: "accountId",
  audience: "https://am.localtest.me:8080/am/oauth2/access_token",
  signingKey: "cGFzc3dvcmQ="
};
// get JWT from nodeState (JWT generation example 2)
var assertionJwt = nodeState.get("assertionJwt");

if (assertionJwt !== null && assertionJwt.length > 0) {
  jwtData["jwt"] = assertionJwt;

  try {
    // returns a map of JWT claims or null if required claims are missing
    // throws NoSuchSecretException if verification key is missing
    var jwtClaims = jwtValidator.validateJwtClaims(jwtData);
    if (jwtClaims !== null) {
      // retrieve and log JWT claim values
      logger.debug("Audience: " + jwtClaims.get("audience"));
      action.goTo("true");
    } else {
      logger.error("Invalid JWT claims");
      action.goTo("false");
    }
  } catch(e) {
    logger.error("Invalid JWT signing key");
    action.goTo("false");
  }
} else {
  logger.error("Error getting assertionJwt");
  action.goTo("false");
}
javascript

Use callbacks

A script can use callbacks to provide or prompt for additional information during the authentication process.

The following script checks for a password and stores the username to shared state for use by subsequent nodes in the authentication journey.

 

Next-generation callback functionality is split between two bindings:

  • callbacksBuilder: Request callbacks. Callbacks are sent automatically when the script completes.

    callbacksBuilder request methods
    • public void textOutputCallback(int messageType, String message)

    • public void suspendedTextOutputCallback(int messageType, String message)

    • public void hiddenValueCallback(String id, String value)

    • public void choiceCallback(String prompt, String[] choices, int defaultChoice, boolean multipleSelectionsAllowed)

    • public void nameCallback(String prompt)

    • public void nameCallback(String prompt, String defaultName)

    • public void passwordCallback(String prompt, boolean echoOn)

    • public void textInputCallback(String prompt)

    • public void textInputCallback(String prompt, String defaultText)

    • public void scriptTextOutputCallback(String message)

    • public void redirectCallback(String redirectUrl, Map redirectData, String method)

    • public void redirectCallback(String redirectUrl, Map redirectData, String method, String statusParameter, String redirectBackUrlCookie)

    • public void metadataCallback(Object outputValue)

    • public void stringAttributeInputCallback(String name, String prompt, String value, Boolean required)

    • public void stringAttributeInputCallback(String name, String prompt, String value, Boolean required, List<String> failedPolicies)

    • public void stringAttributeInputCallback(String name, String prompt, String value, Boolean required, Object policies, Boolean validateOnly)

    • public void stringAttributeInputCallback(String name, String prompt, String value, Boolean required, Object policies, Boolean validateOnly, List<String> failedPolicies)

    • public void numberAttributeInputCallback(String name, String prompt, Double value, Boolean required)

    • public void numberAttributeInputCallback(String name, String prompt, Double value, Boolean required, List<String> failedPolicies)

    • public void numberAttributeInputCallback(String name, String prompt, Double value, Boolean required, Object policies, Boolean validateOnly)

    • public void numberAttributeInputCallback(String name, String prompt, Double value, Boolean required, Object policies, Boolean validateOnly, List<String> failedPolicies)

    • public void booleanAttributeInputCallback(String name, String prompt, Boolean value, Boolean required)

    • public void booleanAttributeInputCallback(String name, String prompt, Boolean value, Boolean required, List<String> failedPolicies)

    • public void booleanAttributeInputCallback(String name, String prompt, Boolean value, Boolean required, Object policies, Boolean validateOnly)

    • public void booleanAttributeInputCallback(String name, String prompt, Boolean value, Boolean required, Object policies, Boolean validateOnly, List<String> failedPolicies)

    • public void confirmationCallback(int messageType, int optionType, int defaultOption)

    • public void confirmationCallback(int messageType, String[] options, int defaultOption)

    • public void confirmationCallback(String prompt, int messageType, int optionType, int defaultOption)

    • public void confirmationCallback(String prompt, int messageType, String[] options, int defaultOption)

    • public void languageCallback(String language, String country)

    • public void idPCallback(String provider, String clientId, String redirectUri, List<String> scope, String nonce, String request, String requestUri, List<String> acrValues, boolean requestNativeAppForUserInfo)

    • public void idPCallback(String provider, String clientId, String redirectUri, List<String> scope, String nonce, String request, String requestUri, List<String> acrValues, boolean requestNativeAppForUserInfo, String token, String tokenType)

    • public void pollingWaitCallback(String waitTime, String message)

    • public void validatedPasswordCallback(String prompt, boolean echoOn, Object policies, Boolean validateOnly)

    • public void validatedPasswordCallback(String prompt, boolean echoOn, Object policies, Boolean validateOnly, List<String> failedPolicies)

    • public void validatedUsernameCallback(String prompt, Object policies, Boolean validateOnly)

    • public void validatedUsernameCallback(String prompt, Object policies, Boolean validateOnly, List<String> failedPolicies)

    • public void httpCallback(String authorizationHeader, String negotiationHeader, String errorCode)

    • public void httpCallback(String authRHeader, String negoName, String negoValue, int errorCode)

    • public void x509CertificateCallback(String prompt)

    • public void x509CertificateCallback(String prompt, X509Certificate certificate)

    • public void x509CertificateCallback(String prompt, X509Certificate certificate, boolean requestSignature)

    • public void consentMappingCallback(Object config, String message, Boolean isRequired)

    • public void consentMappingCallback(String name, String displayName, String icon, String accessLevel, List<String> titles, String message, Boolean isRequired)

    • public void deviceProfileCallback(Boolean metadata, Boolean location, String message)

    • public void kbaCreateCallback(String prompt, List<String> predefinedQuestions, boolean allowUserDefinedQuestions)

    • public void selectIdPCallback(Object providers)

    • public void termsAndConditionsCallback(String version, String terms, String createDate)

  • callbacks: Use isEmpty() to check if callbacks have returned to PingOne Advanced Identity Cloud. Use public get methods to retrieve input from interactive and backchannel callbacks.

    callbacks public get methods
    Method Return type Description

    getBooleanAttributeInputCallbacks()

    List<Boolean>

    The collected Boolean value

    getChoiceCallbacks()

    List<int[]>

    The selected choices as indexes

    getConfirmationCallbacks()

    List<Integer>

    The selected confirmation option

    getConsentMappingCallbacks()

    List<Boolean>

    The collected Boolean value

    getDeviceProfileCallbacks()

    List<String>

    The device information in escaped JSON format

    getHiddenValueCallbacks()

    Map<String, String>

    The map of hiddenValueCallbacks

    getHttpCallbacks()

    List<String>

    The authorization token

    getIdpCallbacks()

    List<Map<String, Object>>

    A map of IDP callback data. Values are:

    • nodeName (String)

    • provider (String)

    • clientId (String)

    • redirectUri (String)

    • scope (List<String>)

    • nonce (String)

    • request (String)

    • requestUri (String)

    • acrValues (List<String>)

    • token (String)

    • tokenType (String)

    • userInfo (String)

    • requestNativeAppForUserInfo (boolean)

    getKbaCreateCallbacks()

    List<Map<String, String>>

    A map of knowledge-based authentication (KBA) responses. Values are selectedQuestion and selectedAnswer.

    getLanguageCallbacks()

    List<String>

    The selected locale

    getNameCallbacks()

    List<String>

    The collected name value

    getNumberAttributeInputCallbacks()

    List<Double>

    The collected numeric value

    getPasswordCallbacks()

    List<String>

    The collected password value

    getSelectIdPCallbacks()

    List<Map<String, Object>>

    A map of selected IDP providers in JSON format

    getStringAttributeInputCallbacks()

    List<String>

    The collected String value

    getTermsAndConditionsCallbacks()

    List<Boolean>

    The Boolean acceptance value

    getTextInputCallbacks()

    List<String>

    The collected text as a String value (can be null)

    getValidatedPasswordCallbacks()

    List<Map<String, Object>>

    A map with the password returned as value (String) and validateOnly(whether to only validate input, or validate and continue journey)

    getValidatedUsernameCallbacks()

    List<Map<String, Object>>

    A map with the username returned as value (String) and validateOnly(whether to only validate input, or validate and continue journey)

    getX509CertificateCallbacks()

    List<Map<String, Object>>

    A map of X.509 certificate callback data. Values are:

    • certificate (X509Certificate)

    • signature (String)

    • reqSignature (Boolean)

The following examples show how to use the callbacksBuilder and callbacks objects to request and receive input from callbacks:

Example nameCallback and passwordCallback
if (callbacks.isEmpty()) {
  // Request callbacks
  callbacksBuilder.nameCallback("User Name", "User Name");
  callbacksBuilder.passwordCallback("Password", false);
} else {
  // Callbacks returned from browser, save username and password
  var username = callbacks.getNameCallbacks().get(0);
  var password = callbacks.getPasswordCallbacks().get(0);

  nodeState.putShared("username", username);

  if (password === null || !password) {
    action.goTo("false");
  } else {
    nodeState.putTransient("password", password);
    action.goTo("true");
  }
}
javascript
Example metadataCallback and textOutputCallback
var data = {
      mfaType: "email"
    };


// Get UUID by enabling `Username as Universal Identifier`
// in a preceding Identity Store Decision node
var uuid = nodeState.get("username");
var identity = idRepository.getIdentity(uuid);

var firstName = identity.getAttributeValues('givenName')[0];

if (callbacks.isEmpty()) {
  // Adds the key-value pair to the JSON response
  callbacksBuilder.metadataCallback(data);

  // Displays a message to the end user
  // Message type: INFORMATION (0), WARNING(1), or ERROR (2)
  callbacksBuilder.textOutputCallback(1, "Hi " + firstName + ", please update your postal code");
}
action.goTo("true");
javascript
Example choiceCallback
var titles = ["Mr", "Mrs", "Ms", "Mx", "Other"];

if (callbacks.isEmpty()) {
  // Request choice from the user
    callbacksBuilder.choiceCallback("Select a title", titles, 0, false);

} else {
  // Callbacks returned from browser, save selected choice
  var index = callbacks.getChoiceCallbacks().get(0)[0];
  var title = titles[index];

  if (title === null || !title) {
    action.goTo("false");
  } else {
    nodeState.putShared("title", title);
    action.goTo("true");
  }
}
javascript
Example hiddenValueCallback and scriptTextOutputCallback
var script = "document.getElementById('clientScriptOutputData').value = navigator.language \n" +
    "document.getElementById('loginButton_0').click()";
try {
    if (callbacks.isEmpty()) {
        callbacksBuilder.hiddenValueCallback("clientScriptOutputData", "false");
        callbacksBuilder.scriptTextOutputCallback(script);

    } else {
        //Browser language can be used to localize custom callback messages
        var lang = callbacks.getHiddenValueCallbacks().get("clientScriptOutputData");
        nodeState.putShared("preferredLanguage", lang);
        action.goTo("true");
    }
    action.goTo("true");

} catch (error) {
    action.goTo("false");
    logger.error("Error getting browser language: " + error.toString());
}
javascript

You can find a list of supported callbacks in Supported callbacks.

Suspend and resume journeys

You can use the action object to suspend an authentication journey and send a message to display to the user. Evaluate the resumedFromSuspend script binding to determine whether to continue the journey.

ActionWrapper suspend(String message)

Suspend the current journey session and send a message to display to the user.

ActionWrapper suspend(String message, SuspensionLogic logic)

Suspend the current journey session and send a message to display to the user. This method also accepts a callable function that references the resume URI at the point of suspension.

 

// Use the resumedFromSuspend object to check if the journey has resumed after suspension
// For example, it returns true after sending an email and receiving a response
if (resumedFromSuspend) {
  action.goTo("true");
} else {
  // Send a message to display to the user and the URL to resume the journey
  action.suspend('Check your email for a magic sign-in link.', (resumeUri) => {
    requestOptions = {
      "method": "POST"
      "body": {
        "resumeUri": resumeUri
      }
    }
    // Use internal mail server to send email
    httpClient.send("https://my.email.service/sendEmail", requestOptions));
  }
}
javascript

Add information to authentication audit log entries

A script can add information to audit log entries with the auditEntryDetail variable. PingOne Advanced Identity Cloud appends the value of the variable to the authentication audit logs, which corresponds to the am-authentication log source.

The auditEntryDetail variable can hold either a string or a JSON object:

  • To add a string to authentication audit log entries:

    auditEntryDetail = "Additional audit information"
    javascript
  • To add a JSON object to authentication audit log entries:

    auditEntryDetail = {
        "transactionStatus": "Success"
    }
    javascript

    Ping Identity recommends that you don’t put spaces in the keys of JSON objects as it prevents you from using a query filter to search additional audit information. For example, the following JSON would not be searchable:

    auditEntryDetail = {
        "Transaction Status": "Success"
    }
    javascript

PingOne Advanced Identity Cloud adds the value of auditEntryDetail to the payload > entries > info > nodeExtraLogging > auditInfo field of the audit log entry:

{
  "payload": {
    "_id": "de5abe95-db97-4354-9d32-aab660ea23a3-4392064",
    "component": "Authentication",
    "entries": [{
      "info": {
        "authLevel": "0",
        "displayName": "Scripted Decision",
        "nodeExtraLogging": {
          "auditInfo": {
            "transactionStatus": "Success"
          }
        },
        "nodeId": "d3a9a765-f5d7-41dd-b936-f862c8b672a2",
        "nodeOutcome": "true",
        "nodeType": "ScriptedDecisionNode",
        "treeName": "Test"
      }
    }],
    "eventName": "AM-NODE-LOGIN-COMPLETED",
    "level": "INFO",
    "principal": [
      "014c54bd-6078-4639-8316-8ce0e7746fa4"
    ],
    "realm": "/alpha",
    "source": "audit",
    "timestamp": "2022-10-31T13:06:38.195Z",
    "topic": "authentication",
    "trackingIds": [
      "de5abe95-db97-4354-9d32-aab660ea23a3-4391910"
    ],
    "transactionId": "bd52ff36-8b34-4819-8e50-68ca1961275b-request-4/0"
  },
  "timestamp": "2022-10-31T13:06:38.195401296Z",
  "type": "application/json",
  "source": "am-authentication"
}
json

Migrate decision node scripts to next-generation scripts

Different bindings are available to the decision node script depending on the scripting engine version; legacy or next-generation.

To migrate legacy scripts to next-generation scripts:

  1. Complete the steps to migrate common bindings as described in Migrate to next-generation scripts.

  2. Next, migrate the bindings specific to decision node scripts by referring to the information in the following table.

    Binding Next-generation change

    New.

    Use static method goTo() to set the script outcome.

    To send callbacks, instead of calling Action.send(), use the new callbacksBuilder functionality.

    New.

    Instead of creating a Callback object and invoking Action.send(), add callbacks using static methods on the callbacksBuilder object; for example nameCallback and passwordCallback. These callbacks are automatically sent when the script completes.

    Use the getIdentity() method on the idRepository object to access attribute values.

    You must now explicitly call store() to persist changes to attribute values.

    New.

    Generate JWT assertions in scripts.

    New.

    Validate JWT assertions in scripts.

    The sharedState and transientState bindings are no longer supported.

    New.

    Use this binding to access the openidm scripting functions supported in IDM.

    New.

    Access the request cookies directly using this binding.

action

Use the action binding to define the exit path from the node and set properties.

var fr = JavaImporter(
    org.forgerock.openam.auth.node.api.Action);

 // Journey continues along the "false" path
action = fr.Action.goTo("false").build();   1
javascript

1 No need to import the Action class to access the goTo method. Instead, call the goTo method directly on the action binding.

callbacksBuilder

Use the callbacksBuilder object instead of importing Callback classes.

Learn more in Use callbacks.

Legacy Next-generation
var fr = JavaImporter(                       1
  org.forgerock.openam.auth.node.api.Action,
  javax.security.auth.callback.NameCallback,
  javax.security.auth.callback.PasswordCallback,
  java.lang.String
);

if (callbacks.isEmpty()) {
  // Request callbacks
  action = fr.Action.send(                   2
    new fr.NameCallback("User Name"),
    new fr.PasswordCallback("Password", false)).build();
} else {
  // Callbacks returned with credentials
  var username =
    fr.String(callbacks.get(0).getName());
  var password =
    fr.String(callbacks.get(1).getPassword());

  sharedState.put("username", username);
  if (password === null || !password) {
    action = fr.Action.goTo("false").build();
  } else {                                   3
    transientState.put("password", password);
    action = fr.Action.goTo("true").build(); 4
  }
}
javascript
if (callbacks.isEmpty()) {                   1
  // Request callbacks
  callbacksBuilder.nameCallback(
    "User Name", "User Name");
  callbacksBuilder.passwordCallback(
    "Password", false);
} else {
  // Callbacks returned with credentials
  var username =
    callbacks.getNameCallbacks().get(0);
  var password =
    callbacks.getPasswordCallbacks().get(0);

  nodeState.putShared("username", username);

  if (password === null || !password) {
    action.goTo("false");                    2
  } else {
    nodeState.putTransient("password",       3
        password);
    action.goTo("true");                     4
  }
}
javascript

1 Use the callbacksBuilder object instead of importing Callback classes.
2 No need to explicitly call send(). The script sends every callback added to the callbacksBuilder when it completes.
3 Use nodeState.putShared() instead of sharedState.put() and nodeState.putTransient() instead of transientState.put().
4 No need to set the outcome, because action.goTo() was invoked.

idRepository

Get an identity from the idRepository object to access attribute values.

Learn more in Access profile data.

Legacy Next-generation
var uuid = "3f5ab61c-1587-44b3-b7d4-675e5159fcca";

var mail = idRepository.getAttribute(
    uuid, "mail");                           1 2

idRepository.setAttribute(username, "mail",
    ["new@example.com"]);                    3
javascript
var uuid = "3f5ab61c-1587-44b3-b7d4-675e5159fcca";

var identity =
    idRepository.getIdentity(uuid);          1

var mail =
    identity.getAttributeValues("mail");     2

 // Does NOT automatically persist data
identity.setAttribute("mail",
    ["new@example.com"]);                    3

try {
    identity.store();                        4
} catch(e) {
    logger.error("Unable to persist attribute. " + e);
}
javascript

1 The idRepository object is no longer used to get attribute values. Instead, use the getIdentity() method of the new org.forgerock.openam.scripting.api.identity.ScriptIdentityRepository interface to get the identity object.
2 Use the identity object, instead of idRepository, to get or set attribute values.
3 Adding or setting attributes on the identity object does not persist data.
4 You must explicitly persist changes by calling the store method.

For more information about the idRepository binding, refer to Access profile data.

nodeState

Use the nodeState binding to get and set the shared state of the journey.

Learn more in Access shared state data.

Legacy Next-generation
 // var username = sharedState.get("username");
                                             1
var username =
    nodeState.get("username").asString();    2
var attributes =                             3
    nodeState.get("objectAttributes").asMap();
javascript
var username = nodeState.get("username");    2
var attributes =
    nodeState.getObject("objectAttributes"); 3
javascript

1 Deprecated sharedState and transientState bindings are no longer available. Use nodeState.get() instead. To store state values, use nodeState.putShared() or nodeState.putTransient() instead of sharedState.put() and transientState.put().
2 No need to call methods such as asString() or asMap().
3 New getObject() method to retrieve a map with values stored across different states. The map is immutable.

To get the UUID from nodeState, precede the journey decision node with a Identity Store Decision node and enable the Username as Universal Identifier property. As a result, when you call nodeState.get('username') the function returns the user’s _id.

Policy condition script API

In addition to the common bindings, a policy condition script has access to the following specific bindings, predefined objects that PingOne Advanced Identity Cloud injects into the script execution context.

Use these objects in a script to get information such as the authorization state of a request, session properties, and user profile data.

You can find an example of how to configure and test a policy condition script in Scripted policy conditions.

PingOne Advanced Identity Cloud has introduced a next-generation scripting engine that offers several benefits, including enhanced script bindings.

The availability and usage of bindings depend on the script engine version of the script: legacy or next-generation. Both versions are described in this section.

You can find information about migrating to the enhanced scripting engine in Migrate to next-generation scripts.

Binding Description Further information

advice

Return the condition advice from the script.

authorized

Return true if the authorization is currently successful, or false if authorization has failed.

Server-side scripts must set a value for authorized before completing.

environment

A map of environment values passed from the client making the authorization request.

identity

Access the data stored in the user’s profile.

resourceURI

The URI of the requested resource.

responseAttributes

Add an attribute to the response to the authorization request.

scriptName

Return the name of the running script.

session

Access the properties for the current session.

ttl

The time-to-live value for the response to a successful authorization.

username

String identifying the user ID of the subject requesting authorization.

-

Access environment data

The environment binding holds data from the client making the authorization request.

Depending on the type of script, the data is formatted as a map containing either the IP or clientId property, for example:

"environment": {
    "IP": [
        "127.0.0.1"
    ]
}
bash

The following example shows how to access the environment data in a policy condition script:

var ipList = environment.get("IP");

if (ipList == null || ipList.length == 0) {
  logger.warn("Missing IP");
  action.goTo("false");
}
else{
  logger.info("IP: " + ipList[0]);
  action.goTo("true");
}
javascript

Access profile data

Server-side authorization scripts can access the profile data of the subject of the authorization request through the methods of the identity object.

To access a subject’s profile data, they must be logged in and their SSO token must be available.

Get attribute values

Return the values of the named attribute for the subject of the authorization request.

// Returns all values as an array, for example: ["test@example.com", "user@example.com"]
identity.getAttributeValues("mail");

// Returns the first value, for example:  test@example.com
identity.getAttributeValues("mail")[0];
javascript
Set attribute values

Set the named attribute to the values specified by the attribute value for the subject of the authorization request.

identity.setAttribute("attrName", ["newValue"]);

// Explicitly persist data
identity.store();
javascript

You must explicitly call store() to persist changes to attribute values.

Add attribute values

Add an attribute value to the list of attribute values associated with the attribute name for the subject of the authorization request.

identity.addAttribute("attrName", ["newValue"]);

// Explicitly persist data
identity.store();
javascript

You must explicitly call store() to persist changes to attribute values.

Access session data

Server-side authorization scripts can access session data for the subject of the authorization request through the methods of the session object.

To access the session data of the subject, they must be logged in and their SSO token must be available.

String session.getProperty(String propertyName)

Retrieve properties from the session associated with the subject of the authorization request. Refer to the following table for example properties and their values.

Session properties and example values
Key Sample value

AMCtxId

e370cca2-02d6-41f9-a244-2b107206bd2a-122934

amlbcookie

01

authInstant

2018-04-04T09:19:05Z

AuthLevel

0

CharSet

UTF-8

clientType

genericHTML

FullLoginURL

/openam/XUI/?realm=alpha#login/

Host

198.51.100.1

HostName

openam.example.com

Locale

en_US

Organization

dc=openam,dc=forgerock,dc=org

Principal

uid=amAdmin,ou=People,dc=openam,dc=forgerock,dc=org

Principals

amAdmin

Service

ldapService

successURL

/openam/console

sun.am.UniversalIdentifier

uid=amAdmin,ou=People,dc=openam,dc=forgerock,dc=org

UserId

amAdmin

UserProfile

Required

UserToken

amAdmin

webhooks

myWebHook

Set authorization responses

Server-side authorization scripts can return information in the response to an authorization request with the following methods:

void responseAttributes.put(String attributeName, Array attributeValue)

Add an attribute to the response to the authorization request.

void advice.put(String adviceKey, Array adviceValues)

Add advice key-value pairs to the response to a failing authorization request.

void ttl(Integer ttlValue)

Add a time-to-live value, which is a timestamp in milliseconds to the response to a successful authorization. After the time-to-live value the decision is no longer valid.

If no value is set, ttlValue defaults to Long.MAX_VALUE (9223372036854775807), which means the decision has no timeout, and can live for as long as the calling client holds on to it. In the case of policy enforcement points, they hold onto the decision for their configured cache timeout.

Migrate policy condition scripts to next-generation scripts

Different bindings are available to a policy condition script depending on the scripting engine version, legacy or next-generation.

To migrate legacy scripts to next-generation scripts:

  1. Complete the steps to migrate common bindings as described in Migrate to next-generation scripts.

  2. Next, migrate the bindings specific to policy condition scripts by referring to the information in the following table.

    Binding Next-generation change

    The binding now returns a Map<String, List<String>> rather than a Map<String, Set<String>>.

    The List format makes it easier to retrieve values because you can access values directly without converting the return objects.

    Attribute values are now returned as a List so that you can access values directly.

    You must now explicitly call store() to persist changes to attribute values.

environment

Use the environment binding to get data from the client making the authorization request.

Learn more in Access environment data.

Legacy Next-generation
var ipSet = environment.get("IP");          1

var userIP = ipSet.iterator().next();       2
javascript
var ipList = environment.get("IP");         1

var userIP = ipList[0];                     2
javascript

1 The environment binding now returns Map<String, List<String>> instead of Map<String, Set<String>>.
2 No need to convert objects by calling toArray()[1] or iterator().next(). Instead you can access values directly, for example, environment.get("KEY")[0].

identity

Use the identity binding to get data about the subject of the authorization request.

The following actions are available to the identity binding:

  • Get attribute values

  • Set attribute values

  • Add attribute values

Legacy Next-generation
 // Returns all values as a set,
 // for example: [test@example.com,user@example.com]
identity.getAttribute("mail").toString();   1

 // Returns the first value
 // for example: test@example.com
identity.getAttribute("mail")
    .iterator().next();                     2

 // persists data
identity.setAttribute("mail",
    ["new@example.com"]);                   3

identity.addAttribute("mail", "user@example.com");
javascript
 // Returns all values as an array,
 // for example: ["test@example.com", "user@example.com"]
identity.getAttributeValues("mail");        1

 // Returns the first value, for example: test@example.com
identity.getAttributeValues("mail")[0];     2

 // Does NOT automatically persist data
identity.setAttribute("mail",
    ["new@example.com"]);                   3

 // Does NOT automatically persist data
identity.addAttribute("mail", "user@example.com");

 // persists data (throws an exception if add/setAttribute failed)
try {
    identity.store();                       4
} catch(e) {
    logger.error("Unable to persist attribute. " + e);
}
javascript

1 The identity object is now a ScriptedIdentityScriptWrapper, which returns a List instead of a Set.
2 No need to convert objects by calling toArray()[1] or iterator().next(). Instead, you can access values directly, for example, identity.getAttributeValues("KEY")[0].
3 Adding or setting attributes on the identity object does not persist data.
4 You must explicitly persist changes by calling the store method.

Learn more about the identity binding in Access profile data.

SAML v2.0 scripting API

The SAML v2.0 scripts all have access to either legacy or next-generation common bindings depending on the script engine for the script type.

Refer to the individual SAML v2.0 script types for specific bindings:

IdP attribute mapper scripting API

The IdP attribute mapper script is a legacy script and therefore has access to all the legacy common bindings in addition to those described here.
Binding Description

hostedEntityId

The entity ID for the hosted IdP.

idpAttributeMapperScriptHelper

An object with methods for IdP attribute mapping. For details, refer to IdpAttributeMapperScriptHelper.

remoteEntityId

The remote entity ID.

session

Represents the user’s single sign-on session object. For details, refer to SSOToken.

IdP adapter scripting API

The IdP adapter script is a legacy script and therefore has access to all the legacy common bindings in addition to those described here.
Binding Description

authnRequest

The original authentication request from the SP. For details, refer to AuthnRequest.

Not available to the preSendFailureResponse function.

faultCode

The fault code returned in the SAML response.

Only available to the preSendFailureResponse function.

faultDetail

Contains the details of the fault returned in the SAML response.

Only available to the preSendFailureResponse function.

hostedEntityId

The entity ID for the hosted IdP.

idpAdapterScriptHelper

An object with methods to provide context when customizing the IdP adapter plugin points. For details, refer to IdpAdapterScriptHelper.

Always present.

relayState

A String representing the relayState used in the redirect.

Not available to the preSingleSignOn or preSendFailureResponse functions.

reqId

The identifier to continue processing if the adapter redirects.

Not available to the preSignResponse or preSendFailureResponse functions.

request

The HttpServletRequest object. Always present.

res

The SAML response. For details, refer to Response.

Only available to the preSignResponse function.

response

The HttpServletResponse object.

Not available to the preSignResponse function.

session

Represents the user’s single sign-on session object. For details, refer to SSOToken.

Not available to the preSingleSignOn or preSendFailureResponse functions.

SP adapter scripting API

The SP adapter script is a legacy script and therefore has access to all the legacy common bindings in addition to those described here.
Binding Description

authnRequest

The original authentication request sent from the SP.

Only available to single sign-on functions.

Refer to AuthnRequest.

binding

The binding used for the name identifier request: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect or urn:oasis:names:tc:SAML:2.0:bindings:SOAP

Not available to single sign-on functions.

failureCode

The failure code associated with the error that has occurred.

Possible values
Constant Value

SUCCESS

0

INVALID_RESPONSE

1

FEDERATION_FAILED_WRITING_ACCOUNT_INFO

3

SSO_FAILED_SESSION_ERROR

4

SSO_FAILED_ATTRIBUTE_MAPPING

5

SSO_FAILED_NO_USER_MAPPING

6

SSO_FAILED_AUTH_USER_INACTIVE

7

SSO_FAILED_AUTH_USER_LOCKED

8

SSO_FAILED_AUTH_ACCOUNT_EXPIRED

9

SSO_FAILED_SESSION_GENERATION

10

SSO_FAILED_META_DATA_ERROR

11

Only available to preSendFailureResponse.

hostedEntityId

The entity ID for the hosted SP.

idpEntityID

The entity ID for the IdP that sends the sign-on request.

idRequest

The ManageNameIDRequest object for the name identifier request.

Only available to postNewNameIDSuccess and postTerminateNameIDSuccess.

idResponse

The ManageNameIDResponse object for the name identifier request.

Only available to postNewNameIDSuccess and postTerminateNameIDSuccess.

isFederation

A boolean indicating whether federation is true if using federation, otherwise false.

Only available to the postSingleSignOnSuccess function.

logoutRequest

The single logout LogoutRequest.

Only available to preSingleLogoutProcess and postSingleLogoutProcess.

logoutResponse

The single logout LogoutResponse.

Only available to preSingleLogoutProcess and postSingleLogoutProcess.

out

The PrintWriter for writing to.

Only available to postSingleSignOnSuccess.

profile

The protocol profile used: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST, urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact or urn:oasis:names:tc:SAML:2.0:bindings:PAOS.

Available to preSingleSignOnProcess, postSingleSignOnSuccess, and postSingleSignOnFailure.

request

The HttpServletRequest object.

Always present.

response

The HttpServletResponse object.

Always present.

session

Contains a representation of the user’s single sign-on session object.

Refer to the SSOToken interface for information about SSO token and authentication information, as well as session-related properties.

Only available to postSingleSignOnSuccess.

spAdapterScriptHelper

The SpAdapterScriptHelper object contains supporting methods that provide context information when customizing the SP adapter extension points.

Always present.

ssoResponse

The SSO Response received from the Identity Provider.

Available to preSingleSignOnProcess, postSingleSignOnSuccess, and postSingleSignOnFailure.

userId

The unique universal ID of the user associated with the request.

Not available to single sign-on functions.

NameID mapper scripting API

The NameID mapper script is a next-generation script and therefore has access to all the next-generation common bindings in addition to those described here.
Binding Description

hostedEntityId

The entity ID for the hosted IdP.

remoteEntityId

The ID of the hosted SAML v2.0 entity.

nameIDFormat

The requested SAML v2.0 NameID format.

session

An instance of ScriptedSession representing the user’s single sign-on session object interface for information about SSO token and authentication information, as well as session-related properties.

Only present when the session object passed by the SAML engine is an SSOToken.

Retrieve session property values with the following ScriptedSession supported method:

public String getProperty(String name)

nameIDScriptHelper

The NameIDScriptHelper binding provides the following supporting methods and constants for customizing the NameID value:

Constants
  • NAMEID_FORMAT_TRANSIENT

  • NAMEID_FORMAT_PERSISTENT

  • NAMEID_FORMAT_UNSPECIFIED

  • NAMEID_FORMAT_EMAIL

Methods
  • public String createNameIdentifier()

  • public String getNameIDValue() throws SAML2Exception

  • public boolean shouldPersistNameIDFormat()

  • public String getNameIDFromSession()

Always present.

identity

An instance of ScriptedIdentityScriptWrapper representing a scriptable implementation of an identity. The identity binding is derived from the session, so it’s only present if the session object is.

The identity object will also be missing if PingOne Advanced Identity Cloud throws an exception during its creation. If this happens, PingOne Advanced Identity Cloud records an entry in the logs.

Manage scripts over REST

PingOne Advanced Identity Cloud provides the /scripts endpoint to manage scripts using REST calls.

Scripts are represented in JSON using standard JSON objects and values.

Each script is identified by a system-generated universally unique identifier (UUID), which must be specified when reading or updating existing scripts. Renaming a script doesn’t affect the UUID.

{
  "_id": "aeb22d32-100c-46c0-ac51-af571889e5b9",
  "name": "MyJavaScript",
  "description": "An example script",
  "script": "dmFyIGEgPSAxMjM7CnZhciBiID0gNDU2Ow==",
  "default": false,
  "language": "JAVASCRIPT",
  "context": "POLICY_CONDITION",
  "createdBy": "null",
  "creationDate": 0,
  "lastModifiedBy": "null",
  "lastModifiedDate": 0,
  "evaluatorVersion": "1.0"
}
json

The values for the fields shown in the example are explained below:

_id

The UUID that PingOne Advanced Identity Cloud generates for the script.

name

The name provided for the script.

description

An optional text string to help identify the script.

script

The source code of the script. The source code is in UTF-8 format and encoded into Base64.

For example, the following script:

var a = 123;
var b = 456;
javascript

becomes dmFyIGEgPSAxMjM7IA0KdmFyIGIgPSA0NTY7 when encoded into Base64.

default

Whether the script is a default script (true) that applies to all realms, or custom (false).

language

The language the script is written in: JAVASCRIPT.

context

The context type of the script.

Table 1. Supported context values
Value Description

AUTHENTICATION_CLIENT_SIDE

Client-side authentication script

AUTHENTICATION_SERVER_SIDE

Server-side authentication script

AUTHENTICATION_TREE_DECISION_NODE

Legacy authentication scripts used by Scripted Decision nodes and Device Match nodes

CONFIG_PROVIDER_NODE

Configuration Provider node script

DEVICE_MATCH_NODE

Next-generation authentication scripts used by Device Match nodes

OAUTH2_ACCESS_TOKEN_MODIFICATION

Access token modification script

OAUTH2_AUTHORIZE_ENDPOINT_DATA_PROVIDER

Script to enhance the data returned from the OAuth 2.0 provider in the authorization request

OAUTH2_EVALUATE_SCOPE

Script to customize the scopes in an OAuth 2.0 access token

OAUTH2_MAY_ACT

Script to add may_act claims to tokens for token exchange

OAUTH2_SCRIPTED_JWT_ISSUE

Script to configure a trusted JWT issuer

OAUTH2_VALIDATE_SCOPE

Script to validate the requested scopes

OIDC_CLAIMS

Modify OIDC claims when issuing an ID token or calling the /userinfo endpoint

LIBRARY

Reuse code with a library script

POLICY_CONDITION

Legacy scripted conditions for authorization policies

POLICY_CONDITION_NEXT_GEN

Next-generation scripted conditions for authorization policies

SAML2_IDP_ADAPTER

Script for customizing the authentication request in a SAML 2.0 journey

SAML2_IDP_ATTRIBUTE_MAPPER

Script for customizing SAML 2.0 attribute mapping

SAML2_NAMEID_MAPPER

Next-generation script to customize the NameID attribute returned in the SAML assertion

SAML2_SP_ADAPTER

Script for customizing the authentication request on the SP side in a SAML 2.0 journey

SCRIPTED_DECISION_NODE

Next-generation authentication scripts used by Scripted Decision nodes

SOCIAL_IDP_PROFILE_TRANSFORMATION

Map fields from the social IDP to fields expected by PingOne Advanced Identity Cloud

createdBy

A string containing the universal identifier DN of the subject that created the script, or null when not used in PingOne Advanced Identity Cloud.

creationDate

An integer containing the creation date and time, in ISO 8601 format, or 0 when not used in PingOne Advanced Identity Cloud.

lastModifiedBy

A string containing the universal identifier DN of the subject that most recently updated the resource type, or null when not used in PingOne Advanced Identity Cloud.

If the script has not been modified since it was created, this property will have the same value as createdBy.

lastModifiedDate

A string containing the last modified date and time, in ISO 8601 format, or 0 when not used in PingOne Advanced Identity Cloud.

If the script has not been modified since it was created, this property will have the same value as creationDate.

evaluatorVersion

A number representing the script engine version: 1.0 for legacy or 2.0 for next-generation. Refer to Next-generation scripts for details.

When invalid or unspecified, the value defaults to 1.0 for all script types except library scripts, which are always 2.0 (next-generation).

Query scripts

To list all the scripts in a realm, as well as any default scripts, perform an HTTP GET to the /json{/realm}/scripts endpoint with a _queryFilter parameter set to true.

The <session-cookie-name> header is required and should contain the SSO token of an administrative user who has access to perform the operation.

$ curl \
--header "<session-cookie-name>: AQICS...NzEz*" \
--header "Accept-API-Version: resource=1.1" \
'https://<tenant-env-fqdn>/am/json/realms/root/realms/alpha/scripts?_queryFilter=true'
{
  "result": [
 {
      "_id": "01e1a3c0-038b-4c16-956a-6c9d89328cff",
      "name": "Authentication Tree Decision Node Script",
      "description": "Default global script for a scripted decision node",
      "script": "LyoKICAtIERhdGE…​",
      "default": true,
      "language": "JAVASCRIPT",
      "context": "AUTHENTICATION_TREE_DECISION_NODE",
      "createdBy": "null",
      "creationDate": 0,
      "lastModifiedBy": "null",
      "lastModifiedDate": 0,
      "evaluatorVersion": "1.0"
    },
    {
      "_id": "aeb22d32-100c-46c0-ac51-af571889e5b9",
      "name": "MyJavaScript",
      "description": "An example script",
      "script": "dmFyIGEgPSAxMjM7CnZhciBiID0gNDU2Ow==",
      "default": false,
      "language": "JAVASCRIPT",
      "context": "POLICY_CONDITION",
      "createdBy": "null",
      "creationDate": 0,
      "lastModifiedBy": "null",
      "lastModifiedDate": 0,
      "evaluatorVersion": "1.0"
    },
  ],
  "resultCount": 2,
  "pagedResultsCookie": null,
  "totalPagedResultsPolicy": "NONE",
  "totalPagedResults": -1,
  "remainingPagedResults": -1
}
bash

You can’t read, update, or delete the default scripts prefixed ForgeRock Internal (UUIDs: 234ba0b-58a1-4cfd-9567-09edde980745 and 1f389a3d-21cf-417c-a6d3-42ea620071f0). These scripts appear in the query but aren’t accessible in Advanced Identity Cloud.

Table 2. Supported _queryFilter fields and operators
Field Supported operators

_id

Equals (eq), Contains (co), Starts with (sw)

name

Equals (eq), Contains (co), Starts with (sw)

description

Equals (eq), Contains (co), Starts with (sw)

script

Equals (eq), Contains (co), Starts with (sw)

language

Equals (eq), Contains (co), Starts with (sw)

context

Equals (eq), Contains (co), Starts with (sw)

Read a script

To read an individual script in a realm, perform an HTTP GET using the /json{/realm}/scripts endpoint, specifying the UUID in the URL.

The <session-cookie-name> header is required and should contain the SSO token of an administrative user who has access to perform the operation.

$ curl \
--header "<session-cookie-name>: AQICS...NzEz*" \
--header "Accept-API-Version: resource=1.1" \
'https://<tenant-env-fqdn>/am/json/realms/root/realms/alpha/scripts/aeb22d32-100c-46c0-ac51-af571889e5b9'
{
  "_id": "aeb22d32-100c-46c0-ac51-af571889e5b9",
  "name": "MyJavaScript",
  "description": "An example script",
  "script": "dmFyIGEgPSAxMjM7CnZhciBiID0gNDU2Ow==",
  "default": false,
  "language": "JAVASCRIPT",
  "context": "POLICY_CONDITION",
  "createdBy": "null",
  "creationDate": 0,
  "lastModifiedBy": "null",
  "lastModifiedDate": 0,
  "evaluatorVersion": "1.0"
}
bash

You can’t read, update, or delete the default scripts prefixed ForgeRock Internal (UUIDs: 234ba0b-58a1-4cfd-9567-09edde980745 and 1f389a3d-21cf-417c-a6d3-42ea620071f0). These scripts appear in the query but aren’t accessible in Advanced Identity Cloud.

Validate a script

To validate a script, perform an HTTP POST using the /json{/realm}/scripts endpoint, with an _action parameter set to validate. Include a JSON representation of the script and the script language, JAVASCRIPT, in the POST data.

The value for script must be in UTF-8 format and then encoded into Base64.

The <session-cookie-name> header is required and should contain the SSO token of an administrative user who has access to perform the operation.

$ curl \
--request POST \
--header "Content-Type: application/json" \
--header "<session-cookie-name>: AQICS...NzEz*" \
--header "Accept-API-Version: resource=1.1" \
--data '{
    "script": "dmFyIGEgPSAxMjM7dmFyIGIgPSA0NTY7Cg==",
    "language": "JAVASCRIPT"
}' \
'https://<tenant-env-fqdn>/am/json/realms/root/realms/alpha/scripts/?_action=validate'
{
    "success": true
}
bash

If the script is valid the JSON response contains a success key with a value of true.

If the script is invalid the JSON response contains a success key with a value of false, and an indication of the problem and where it occurs, as shown below:

$ curl \
--request POST \
--header "Content-Type: application/json" \
--header "<session-cookie-name>: AQICS...NzEz*" \
--header "Accept-API-Version: resource=1.1" \
--data '{
    "script": "dmFyIGEgPSAxMjM7dmFyIGIgPSA0NTY7ID1WQUxJREFUSU9OIFNIT1VMRCBGQUlMPQo=",
    "language": "JAVASCRIPT"
}' \
'https://<tenant-env-fqdn>/am/json/realms/root/realms/alpha/scripts/?_action=validate'
{
    "success": false,
    "errors": [
        {
            "line": 1,
            "column": 27,
            "message": "syntax error"
        }
    ]
}
bash

Create a script

To create a script in a realm, perform an HTTP POST using the /json{/realm}/scripts endpoint, with an _action parameter set to create. Include a JSON representation of the script in the POST data.

The value for script must be in UTF-8 format and encoded into Base64.

The <session-cookie-name> header is required and should contain the SSO token of an administrative user who has access to perform the operation.

$ curl \
--request POST \
--header "Content-Type: application/json" \
--header "<session-cookie-name>: AQICS...NzEz*" \
--header "Accept-API-Version: resource=1.1" \
--data '{
    "name": "MyJavaScript",
    "script": "dmFyIGEgPSAxMjM7CnZhciBiID0gNDU2Ow==",
    "language": "JAVASCRIPT",
    "context": "POLICY_CONDITION",
    "description": "An example script"
}' \
'https://<tenant-env-fqdn>/am/json/realms/root/realms/alpha/scripts/?_action=create'
{
    "_id": "0168d494-015a-420f-ae5a-6a2a5c1126af",
    "_rev": "-482518750",
    "name": "MyJavaScript",
    "description": "An example script",
    "script": "dmFyIGEgPSAxMjM7CnZhciBiID0gNDU2Ow==",
    "default": false,
    "language": "JAVASCRIPT",
    "context": "POLICY_CONDITION",
    "createdBy": "id=ed6816a3-c158-48e0-8402-b2f971b5b492,ou=user,ou=am-config",
    "creationDate": 1687779600329,
    "lastModifiedBy": "id=ed6816a3-c158-48e0-8402-b2f971b5b492,ou=user,ou=am-config",
    "lastModifiedDate": 1687779600329,
    "evaluatorVersion": "1.0"
}
bash

Update a script

To update a script, perform an HTTP PUT using the /json{/realm}/scripts endpoint, specifying the UUID in both the URL and the PUT body. Include a JSON representation of the updated script in the PUT data alongside the UUID.

The <session-cookie-name> header is required and should contain the SSO token of an administrative user who has access to perform the operation.

$ curl \
--header "<session-cookie-name>: AQICS...NzEz*" \
--header "Content-Type: application/json" \
--header "Accept-API-Version: resource=1.1" \
--request PUT \
--data '{
    "name": "MyUpdatedJavaScript",
    "script": "dmFyIGEgPSAxMjM7CnZhciBiID0gNDU2Ow==",
    "language": "JAVASCRIPT",
    "context": "POLICY_CONDITION",
    "description": "An updated example script configuration"
}' \
'https://<tenant-env-fqdn>/am/json/realms/root/realms/alpha/scripts/aeb22d32-100c-46c0-ac51-af571889e5b9'
{
  "_id": "aeb22d32-100c-46c0-ac51-af571889e5b9",
  "name": "UpdatedJavaScript",
  "description": "An updated example script",
  "script": "dmFyIGEgPSAxMjM7CnZhciBiID0gNDU2Ow==",
  "default": false,
  "language": "JAVASCRIPT",
  "context": "POLICY_CONDITION",
  "createdBy": "null",
  "creationDate": 0,
  "lastModifiedBy": "id=ed6816a3-c158-48e0-8402-b2f971b5b492,ou=user,ou=am-config",
  "lastModifiedDate": 1687792307783,
  "evaluatorVersion": "1.0"
}
bash

You can’t read, update, or delete the default scripts prefixed ForgeRock Internal (UUIDs: 234ba0b-58a1-4cfd-9567-09edde980745 and 1f389a3d-21cf-417c-a6d3-42ea620071f0). These scripts appear in the query but aren’t accessible in Advanced Identity Cloud.

Delete a script

To delete a script, perform an HTTP DELETE using the /json{/realm}/scripts endpoint, specifying the UUID in the URL.

The <session-cookie-name> header is required and should contain the SSO token of an administrative user who has access to perform the operation.

$ curl \
--request DELETE \
--header "<session-cookie-name>: AQICS...NzEz*" \
--header "Accept-API-Version: resource=1.1" \
'https://<tenant-env-fqdn>/am/json/realms/root/realms/alpha/scripts/aeb22d32-100c-46c0-ac51-af571889e5b9'
{}
bash

You can’t read, update, or delete the default scripts prefixed ForgeRock Internal (UUIDs: 234ba0b-58a1-4cfd-9567-09edde980745 and 1f389a3d-21cf-417c-a6d3-42ea620071f0). These scripts appear in the query but aren’t accessible in Advanced Identity Cloud.

Sample scripts

The following sample scripts demonstrate how to extend PingOne Advanced Identity Cloud.

The comments describe the variables available in the execution context of the script, so use them for reference even if your script’s function differs from the sample:

amazon-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('user_id'));
    normalizedProfileData.put('displayName', rawProfile.get('name'));
    normalizedProfileData.put('email', rawProfile.get('email'));
    normalizedProfileData.put('username', rawProfile.get('email'));

    return normalizedProfileData;
}());
javascript

Open amazon-profile-normalization.js in your browser.

apple-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required to be not empty:
 *          username, givenName, familyName, email.
 *
 *          From RFC4517: A value of the Directory String syntax is a string of one or more
 *          arbitrary characters from the Universal Character Set (UCS).
 *          A zero-length character string is not permitted.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    var email = null;
    var firstName = null;
    var lastName = null;
    var username = null;
    var name;

    if(rawProfile.isDefined('email') && rawProfile.get('email').isNotNull()) { // User can elect to not share their email
        email = rawProfile.get('email').asString();
        username = email;
    } else {
        throw new Error('Email is required');
    }
    if (rawProfile.isDefined('name') && rawProfile.get('name').isNotNull()) {
        if (rawProfile.name.isDefined('firstName') && rawProfile.get('firstName').isNotNull()) {
            firstName = rawProfile.get('name').get('firstName').asString()
        }
        if (rawProfile.name.isDefined('lastName') && rawProfile.get('lastName').isNotNull()) {
            lastName = rawProfile.get('name').get('lastName').asString()
        }
    }

    var hasFirstName = firstName && firstName.trim().length > 0
    var hasLastName = lastName && lastName.trim().length > 0
    name = (hasFirstName ? firstName : '') + (hasLastName ? (hasFirstName ? ' ' : '') + lastName : '')
    name =  name ? name : ' '

    normalizedProfileData.put('id', rawProfile.sub);
    normalizedProfileData.put('displayName', name);
    normalizedProfileData.put('email', email);
    if (firstName !== null) {
        normalizedProfileData.put('givenName', firstName);
    }
    if (lastName !== null) {
        normalizedProfileData.put('familyName', lastName);
    }
    normalizedProfileData.put('username', username);

    return normalizedProfileData;
}());
javascript

Open apple-profile-normalization.js in your browser.

authentication-client-side.js

View script
/*
 * Copyright 2016-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */
/* Default Authentication client side script to use as a template for new scripts */
javascript

Open authentication-client-side.js in your browser.

authentication-server-side.js

View script
/*
 * Copyright 2015-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

var START_TIME = 9;  // 9am
var END_TIME   = 17; // 5pm
var longitude, latitude;
var localTime;

logger.message("Starting scripted authentication");
logger.message("User: " + username);

var userPostalAddress = getUserPostalAddress();
logger.message("User address: " + userPostalAddress);

getLongitudeLatitudeFromUserPostalAddress();
getLocalTime();

logger.message("Current time at the users location: " + localTime.getHours());
if (localTime.getHours() < START_TIME || localTime.getHours() > END_TIME) {
    logger.error("Login forbidden outside work hours!");
    authState = FAILED;
} else {
    logger.message("Authentication allowed!");
    authState = SUCCESS;
}

function getLongitudeLatitudeFromUserPostalAddress() {

    var request = new org.forgerock.http.protocol.Request();
    request.setUri("http://maps.googleapis.com/maps/api/geocode/json?address=" + encodeURIComponent(userPostalAddress));
  	request.setMethod("GET");
  	//the above URI has to be extended with an API_KEY if used in a frequent manner
  	//see documentation: https://developers.google.com/maps/documentation/geocoding/intro

    var response = httpClient.send(request).get();
    logResponse(response);

    var geocode = JSON.parse(response.getEntity().getString());
    var i;
    for (i = 0; i < geocode.results.length; i++) {
        var result = geocode.results[i];
        latitude = result.geometry.location.lat;
        longitude = result.geometry.location.lng;

   	    logger.message("latitude:" + latitude + " longitude:" + longitude);
    }
}

function getLocalTime() {

    var now = new Date().getTime() / 1000;
    var location = "location=" + latitude + "," + longitude;
    var timestamp = "timestamp=" + now;

    var request = new org.forgerock.http.protocol.Request();
    request.setUri("https://maps.googleapis.com/maps/api/timezone/json?" + location + "&" + timestamp);
  	request.setMethod("GET");
  	//the above URI has to be extended with an API_KEY if used in a frequent manner
  	//see documentation: https://developers.google.com/maps/documentation/timezone/intro

    var response = httpClient.send(request).get();
    logResponse(response);

    var timezone = JSON.parse(response.getEntity().getString());
    var localTimestamp = parseInt(now) + parseInt(timezone.dstOffset) + parseInt(timezone.rawOffset);
    localTime = new Date(localTimestamp*1000);
}

function getUserPostalAddress() {
    var userAddressSet = idRepository.getAttribute(username, "postalAddress");
    if (userAddressSet == null || userAddressSet.isEmpty()) {
        logger.warning("No address specified for user: " + username);
        return false;
    }
    return userAddressSet.iterator().next()
}

function logResponse(response) {
    logger.message("User REST Call. Status: " + response.getStatus() + ", Body: " + response.getEntity().getString());
}
javascript

Open authentication-server-side.js in your browser.

authentication-tree-decision-node.js

View script
/*
  - Data made available by nodes that have already executed are available in the sharedState variable.
  - The script should set outcome to either "true" or "false".
 */

outcome = "true";
javascript

Open authentication-tree-decision-node.js in your browser.

config-provider-node.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/**
 * The following script is a simplified template for understanding how to build
 * up a config Map object with custom values. The Config Provider Node will then
 * provide this config Map to the desired node type. It is important that the Map
 * you build here is named 'config'.
 *
 * Defined variables:
 *
 * nodeState - Node State (1)
 *           Always present, this represents the current values stored in the node state.
 *
 * idRepository - Profile Data (2)
 *           Always present, a repository to retrieve user information.
 *
 * secrets - Credentials and Secrets (3)
 *           Always present, an interface to access the Secrets API from a scripting context.
 *
 * requestHeaders (4) - Map (5)
 *           Always present, an object that provides methods for accessing headers in the login request.
 *
 * logger - Debug Logging (6)
 *          Always present, the debug logger instance.
 *
 * httpClient - HTTP Client (7)
 *          Always present, the HTTP client that can be used to make external HTTP requests.
 *
 * realm - String (primitive).
 *          Always present, the name of the realm the user is authenticating to.
 *
 * existingSession - Map<String, String> (5)
 *          Present if the request contains the session cookie, the user's session object. The returned map from
 *          SSOToken.getProperties() (8)
 *
 * requestParameters - Map (5)
 *          Always present, the object that contains the authentication request parameters.
 *
 *
 * Outputs:
 *
 * config - Map (5)
 *           Define and fill a Map object named 'config' with custom values, this will define the configuration for the
 *           associated node selected in the ConfigProviderNode.
 *
 * Reference:
 * (1) Node State - https://backstage.forgerock.com/docs/idcloud-am/latest/authentication-guide/scripting-api-node.html#scripting-api-node-nodeState
 * (2) Profile Data - https://backstage.forgerock.com/docs/am/7.1/authentication-guide/scripting-api-node.html#scripting-api-node-id-repo
 * (3) Credentials and Secrets - https://backstage.forgerock.com/docs/am/7.1/authentication-guide/scripting-api-node.html#scripting-api-authn-secrets
 * (4) Request Headers - https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * (5) Map - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Map.html
 * (6) Debug Logging - https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * (7) HTTP Client - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/http/Client.html.
 * (8) SSOToken - https://backstage.forgerock.com/docs/am/7/apidocs/com/iplanet/sso/SSOToken.html.
 */

config = {
    "key0": {"subKey": "value0"},
    "key1": "value1"
};
javascript

Open config-provider-node.js in your browser.

deviceIdMatch-client-side.js

View script
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2009 Sun Microsystems Inc. All Rights Reserved
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * https://opensso.dev.java.net/public/CDDLv1.0.html or
 * opensso/legal/CDDLv1.0.txt
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at opensso/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 */
/*
 * Portions Copyrighted 2013 Syntegrity.
 * Portions Copyrighted 2013-2025 Ping Identity Corporation.
 */

var collectScreenInfo = function () {
        var screenInfo = {};
        if (screen) {
            if (screen.width) {
                screenInfo.screenWidth = screen.width;
            }

            if (screen.height) {
                screenInfo.screenHeight = screen.height;
            }

            if (screen.pixelDepth) {
                screenInfo.screenColourDepth = screen.pixelDepth;
            }
        } else {
            console.warn("Cannot collect screen information. screen is not defined.");
        }
        return screenInfo;
    },
    collectTimezoneInfo = function () {
        var timezoneInfo =  {}, offset = new Date().getTimezoneOffset();

        if (offset) {
            timezoneInfo.timezone = offset;
        } else {
            console.warn("Cannot collect timezone information. timezone is not defined.");
        }

        return timezoneInfo;
    },
    collectBrowserPluginsInfo = function () {

        if (navigator && navigator.plugins) {
            var pluginsInfo = {}, i, plugins = navigator.plugins;
            pluginsInfo.installedPlugins = "";

            for (i = 0; i < plugins.length; i++) {
                pluginsInfo.installedPlugins = pluginsInfo.installedPlugins + plugins[i].filename + ";";
            }

            return pluginsInfo;
        } else {
            console.warn("Cannot collect browser plugin information. navigator.plugins is not defined.");
            return {};
        }

    },
// Getting geolocation takes some time and is done asynchronously, hence need a callback which is called once geolocation is retrieved.
    collectGeolocationInfo = function (callback) {
        var geolocationInfo = {},
            successCallback = function(position) {
                geolocationInfo.longitude = position.coords.longitude;
                geolocationInfo.latitude = position.coords.latitude;
                callback(geolocationInfo);
            }, errorCallback = function(error) {
                console.warn("Cannot collect geolocation information. " + error.code + ": " + error.message);
                callback(geolocationInfo);
            };
        if (navigator && navigator.geolocation) {
            // NB: If user chooses 'Not now' on Firefox neither callback gets called
            //     https://bugzilla.mozilla.org/show_bug.cgi?id=675533
            navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
        } else {
            console.warn("Cannot collect geolocation information. navigator.geolocation is not defined.");
            callback(geolocationInfo);
        }
    },
    collectBrowserFontsInfo = function () {
        var fontsInfo = {}, i, fontsList = ["cursive","monospace","serif","sans-serif","fantasy","default","Arial","Arial Black",
            "Arial Narrow","Arial Rounded MT Bold","Bookman Old Style","Bradley Hand ITC","Century","Century Gothic",
            "Comic Sans MS","Courier","Courier New","Georgia","Gentium","Impact","King","Lucida Console","Lalit",
            "Modena","Monotype Corsiva","Papyrus","Tahoma","TeX","Times","Times New Roman","Trebuchet MS","Verdana",
            "Verona"];
        fontsInfo.installedFonts = "";

        for (i = 0; i < fontsList.length; i++) {
            if (fontDetector.detect(fontsList[i])) {
                fontsInfo.installedFonts = fontsInfo.installedFonts + fontsList[i] + ";";
            }
        }
        return fontsInfo;
    },
    devicePrint = {};

devicePrint.screen = collectScreenInfo();
devicePrint.timezone = collectTimezoneInfo();
devicePrint.plugins = collectBrowserPluginsInfo();
devicePrint.fonts = collectBrowserFontsInfo();

if (navigator.userAgent) {
    devicePrint.userAgent = navigator.userAgent;
}
if (navigator.appName) {
    devicePrint.appName = navigator.appName;
}
if (navigator.appCodeName) {
    devicePrint.appCodeName = navigator.appCodeName;
}
if (navigator.appVersion) {
    devicePrint.appVersion = navigator.appVersion;
}
if (navigator.appMinorVersion) {
    devicePrint.appMinorVersion = navigator.appMinorVersion;
}
if (navigator.buildID) {
    devicePrint.buildID = navigator.buildID;
}
if (navigator.platform) {
    devicePrint.platform = navigator.platform;
}
if (navigator.cpuClass) {
    devicePrint.cpuClass = navigator.cpuClass;
}
if (navigator.oscpu) {
    devicePrint.oscpu = navigator.oscpu;
}
if (navigator.product) {
    devicePrint.product = navigator.product;
}
if (navigator.productSub) {
    devicePrint.productSub = navigator.productSub;
}
if (navigator.vendor) {
    devicePrint.vendor = navigator.vendor;
}
if (navigator.vendorSub) {
    devicePrint.vendorSub = navigator.vendorSub;
}
if (navigator.language) {
    devicePrint.language = navigator.language;
}
if (navigator.userLanguage) {
    devicePrint.userLanguage = navigator.userLanguage;
}
if (navigator.browserLanguage) {
    devicePrint.browserLanguage = navigator.browserLanguage;
}
if (navigator.systemLanguage) {
    devicePrint.systemLanguage = navigator.systemLanguage;
}

// Attempt to collect geo-location information and return this with the data collected so far.
// Otherwise, if geo-location fails or takes longer than 30 seconds, auto-submit the data collected so far.
autoSubmitDelay = 30000;
output.value = JSON.stringify(devicePrint);
collectGeolocationInfo(function(geolocationInfo) {
    devicePrint.geolocation = geolocationInfo;
    output.value = JSON.stringify(devicePrint);
    submit();
});
javascript

Open deviceIdMatch-client-side.js in your browser.

deviceIdMatch-server-side.js

View script
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2009 Sun Microsystems Inc. All Rights Reserved
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * https://opensso.dev.java.net/public/CDDLv1.0.html or
 * opensso/legal/CDDLv1.0.txt
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at opensso/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 */
/*
 * Portions Copyrighted 2013 Syntegrity.
 * Portions Copyrighted 2013-2025 Ping Identity Corporation.
 */

var ScalarComparator = {}, ScreenComparator = {}, MultiValueComparator = {}, UserAgentComparator = {}, GeolocationComparator = {};

var config = {
    profileExpiration: 30,              //in days
    maxProfilesAllowed: 5,
    maxPenaltyPoints: 0,
    attributes: {
        screen: {
            required: true,
            comparator: ScreenComparator,
            args: {
                penaltyPoints: 50
            }
        },
        plugins: {
            installedPlugins: {
                required: false,
                comparator: MultiValueComparator,
                args: {
                    maxPercentageDifference: 10,
                    maxDifferences: 5,
                    penaltyPoints: 100
                }
            }
        },
        fonts: {
            installedFonts: {
                required: false,
                comparator: MultiValueComparator,
                args: {
                    maxPercentageDifference: 10,
                    maxDifferences: 5,
                    penaltyPoints: 100
                }
            }
        },
        timezone: {
            timezone: {
                required: false,
                comparator: ScalarComparator,
                args: {
                    penaltyPoints: 100
                }
            }
        },
        userAgent: {
            required: true,
            comparator: UserAgentComparator,
            args: {
                ignoreVersion: true,
                penaltyPoints: 100
            }
        },
        geolocation: {
            required: false,
            comparator: GeolocationComparator,
            args: {
                allowedRange: 100,			//in miles
                penaltyPoints: 100
            }
        }
    }
};

//---------------------------------------------------------------------------//
//                           Comparator functions                            //
//---------------------------------------------------------------------------//

var all, any, calculateDistance, calculateIntersection, calculatePercentage, nullOrUndefined, splitAndTrim,
    undefinedLocation;

// ComparisonResult

/**
 * Constructs an instance of a ComparisonResult with the given penalty points.
 *
 * @param penaltyPoints (Number) The penalty points for the comparison (defaults to 0).
 * @param additionalInfoInCurrentValue (boolean) Whether the current value contains more information
 *                                               than the stored value (defaults to false).
 */
function ComparisonResult() {

    var penaltyPoints = 0,
        additionalInfoInCurrentValue = false;

    if (arguments[0] !== undefined && arguments[1] !== undefined) {
        penaltyPoints = arguments[0];
        additionalInfoInCurrentValue = arguments[1];
    }

    if (arguments[0] !== undefined && arguments[1] === undefined) {
        if (typeof(arguments[0]) === "boolean") {
            additionalInfoInCurrentValue = arguments[0];
        } else {
            penaltyPoints = arguments[0];
        }
    }

    this.penaltyPoints = penaltyPoints;
    this.additionalInfoInCurrentValue = additionalInfoInCurrentValue;

}

ComparisonResult.ZERO_PENALTY_POINTS = new ComparisonResult(0);

/**
 * Static method for functional programming.
 *
 * @return boolean true if comparisonResult.isSuccessful().
 */
ComparisonResult.isSuccessful =  function(comparisonResult) {
    return comparisonResult.isSuccessful();
};


/**
 * Static method for functional programming.
 *
 * @return boolean true if comparisonResult.additionalInfoInCurrentValue.
 */
ComparisonResult.additionalInfoInCurrentValue =  function(comparisonResult) {
    return comparisonResult.additionalInfoInCurrentValue;
};

/**
 * Comparison function that can be provided as an argument to array.sort
 */
ComparisonResult.compare = function(first, second) {
    if (nullOrUndefined(first) && nullOrUndefined(second)) {
        return 0;
    } else if (nullOrUndefined(first)) {
        return -1;
    } else if (nullOrUndefined(second)) {
        return 1;
    } else {
        if (first.penaltyPoints !== second.penaltyPoints) {
            return first.penaltyPoints - second.penaltyPoints;
        } else {
            return (first.additionalInfoInCurrentValue ? 1 : 0) - (second.additionalInfoInCurrentValue ? 1 : 0);
        }
    }
};

/**
 * Amalgamates the given ComparisonResult into this ComparisonResult.
 *
 * @param comparisonResult The ComparisonResult to include.
 */
ComparisonResult.prototype.addComparisonResult = function(comparisonResult) {
    this.penaltyPoints += comparisonResult.penaltyPoints;
    if (comparisonResult.additionalInfoInCurrentValue) {
        this.additionalInfoInCurrentValue = comparisonResult.additionalInfoInCurrentValue;
    }
};

/**
 * Returns true if no penalty points have been assigned for the comparison.
 *
 * @return boolean true if the comparison was successful.
 */
ComparisonResult.prototype.isSuccessful = function() {
    return nullOrUndefined(this.penaltyPoints) || this.penaltyPoints === 0;
};

/**
 * Compares two simple objects (String|Number) and if they are equal then returns a ComparisonResult with zero
 * penalty points assigned, otherwise returns a ComparisonResult with the given number of penalty points assigned.
 *
 * @param currentValue (String|Number) The current value.
 * @param storedValue (String|Number) The stored value.
 * @param config: {
 *            "penaltyPoints": (Number) The number of penalty points.
 *        }
 * @return ComparisonResult.
 */
ScalarComparator.compare = function (currentValue, storedValue, config) {
    if (logger.messageEnabled()) {
        logger.message("StringComparator.compare:currentValue: " + JSON.stringify(currentValue));
        logger.message("StringComparator.compare:storedValue: " + JSON.stringify(storedValue));
        logger.message("StringComparator.compare:config: " + JSON.stringify(config));
    }
    if (config.penaltyPoints === 0) {
        return ComparisonResult.ZERO_PENALTY_POINTS;
    }

    if (!nullOrUndefined(storedValue)) {
        if (nullOrUndefined(currentValue) || currentValue !== storedValue) {
            return new ComparisonResult(config.penaltyPoints);
        }
    } else if (!nullOrUndefined(currentValue)) {
        return new ComparisonResult(true);
    }

    return ComparisonResult.ZERO_PENALTY_POINTS;
};

/**
 * Compares two screens and if they are equal then returns a ComparisonResult with zero penalty points assigned,
 * otherwise returns a ComparisonResult with the given number of penalty points assigned.
 *
 * @param currentValue: {
 *            "screenWidth": (Number) The current client screen width.
 *            "screenHeight": (Number) The current client screen height.
 *            "screenColourDepth": (Number) The current client screen colour depth.
 *        }
 * @param storedValue: {
 *            "screenWidth": (Number) The stored client screen width.
 *            "screenHeight": (Number) The stored client screen height.
 *            "screenColourDepth": (Number) The stored client screen colour depth.
 *        }
 * @param config: {
 *            "penaltyPoints": (Number) The number of penalty points.
 *        }
 * @return ComparisonResult
 */
ScreenComparator.compare = function (currentValue, storedValue, config) {
    if (logger.messageEnabled()) {
        logger.message("ScreenComparator.compare:currentValue: " + JSON.stringify(currentValue));
        logger.message("ScreenComparator.compare:storedValue: " + JSON.stringify(storedValue));
        logger.message("ScreenComparator.compare:config: " + JSON.stringify(config));
    }

    if (nullOrUndefined(currentValue)) {
        currentValue = {screenWidth: null, screenHeight: null, screenColourDepth: null};
    }
    if (nullOrUndefined(storedValue)) {
        storedValue = {screenWidth: null, screenHeight: null, screenColourDepth: null};
    }

    var comparisonResults = [
        ScalarComparator.compare(currentValue.screenWidth, storedValue.screenWidth, config),
        ScalarComparator.compare(currentValue.screenHeight, storedValue.screenHeight, config),
        ScalarComparator.compare(currentValue.screenColourDepth, storedValue.screenColourDepth, config)];

    if (all(comparisonResults, ComparisonResult.isSuccessful)) {
        return new ComparisonResult(any(comparisonResults, ComparisonResult.additionalInfoInCurrentValue));
    } else {
        return new ComparisonResult(config.penaltyPoints);
    }
};

/**
 * Splits both values using delimiter, trims every value and compares collections of values.
 * Returns zero-result for same multi-value attributes.
 *
 * If collections are not same checks if number of differences is less or equal maxDifferences or
 * percentage of difference is less or equal maxPercentageDifference.
 *
 * If yes then returns zero-result with additional info, else returns penaltyPoints-result.
 *
 * @param currentValue: (String) The current value.
 * @param storedValue: (String) The stored value.
 * @param config: {
 *            "maxPercentageDifference": (Number) The max difference percentage in the values,
 *                                                before the penalty is assigned.
 *            "maxDifferences": (Number) The max number of differences in the values,
 *                                       before the penalty points are assigned.
 *            "penaltyPoints": (Number) The number of penalty points.
  *        }
 * @return ComparisonResult
 */
MultiValueComparator.compare = function (currentValue, storedValue, config) {
    if (logger.messageEnabled()) {
        logger.message("MultiValueComparator.compare:currentValue: " + JSON.stringify(currentValue));
        logger.message("MultiValueComparator.compare:storedValue: " + JSON.stringify(storedValue));
        logger.message("MultiValueComparator.compare:config: " + JSON.stringify(config));
    }

    var delimiter = ";",
        currentValues = splitAndTrim(currentValue, delimiter),
        storedValues = splitAndTrim(storedValue, delimiter),
        maxNumberOfElements = Math.max(currentValues.length, storedValues.length),
        numberOfTheSameElements = calculateIntersection(currentValues, storedValues).length,
        numberOfDifferences = maxNumberOfElements - numberOfTheSameElements,
        percentageOfDifferences = calculatePercentage(numberOfDifferences, maxNumberOfElements);

    if (nullOrUndefined(storedValue) && !nullOrUndefined(currentValue)) {
        return new ComparisonResult(true);
    }

    if (logger.messageEnabled()) {
        logger.message(numberOfTheSameElements + " of " + maxNumberOfElements + " are same");
    }

    if (maxNumberOfElements === 0) {
        logger.message("Ignored because no attributes found in both profiles");
        return ComparisonResult.ZERO_PENALTY_POINTS;
    }

    if (numberOfTheSameElements === maxNumberOfElements) {
        logger.message("Ignored because all attributes are same");
        return ComparisonResult.ZERO_PENALTY_POINTS;
    }

    if (numberOfDifferences > config.maxDifferences) {
        if (logger.messageEnabled()) {
            logger.message("Would be ignored if not more than " + config.maxDifferences + " differences");
        }
        return new ComparisonResult(config.penaltyPoints);
    }

    if (percentageOfDifferences > config.maxPercentageDifference) {
        if (logger.messageEnabled()) {
            logger.message(percentageOfDifferences + " percents are different");
            logger.message("Would be ignored if not more than " + config.maxPercentageDifference + " percent");
        }
        return new ComparisonResult(config.penaltyPoints);
    }

    if (logger.messageEnabled()) {
        logger.message("Ignored because number of differences(" + numberOfDifferences + ") not more than "
            + config.maxDifferences);
        logger.message(percentageOfDifferences + " percents are different");
        logger.message("Ignored because not more than " + config.maxPercentageDifference + " percent");
    }
    return new ComparisonResult(true);
};

/**
 * Compares two User Agent Strings and if they are equal then returns a ComparisonResult with zero penalty
 * points assigned, otherwise returns a ComparisonResult with the given number of penalty points assigned.
 *
 * @param currentValue (String) The current value.
 * @param storedValue (String) The stored value.
 * @param config: {
 *            "ignoreVersion": (boolean) If the version numbers in the User Agent Strings should be ignore
 *                                       in the comparison.
 *            "penaltyPoints": (Number) The number of penalty points.
 *        }
 * @return A ComparisonResult.
 */
UserAgentComparator.compare = function (currentValue, storedValue, config) {
    if (logger.messageEnabled()) {
        logger.message("UserAgentComparator.compare:currentValue: " + JSON.stringify(currentValue));
        logger.message("UserAgentComparator.compare:storedValue: " + JSON.stringify(storedValue));
        logger.message("UserAgentComparator.compare:config: " + JSON.stringify(config));
    }

    if (config.ignoreVersion) {
        // remove version number
        currentValue = nullOrUndefined(currentValue) ? null : currentValue.replace(/[\d\.]+/g, "").trim();
        storedValue = nullOrUndefined(storedValue) ? null : storedValue.replace(/[\d\.]+/g, "").trim();
    }

    return ScalarComparator.compare(currentValue, storedValue, config);
};

/**
 * Compares two locations, taking into account a degree of difference.
 *
 * @param currentValue: {
 *            "latitude": (Number) The current latitude.
 *            "longitude": (Number) The current longitude.
 *        }
 * @param storedValue: {
 *            "latitude": (Number) The stored latitude.
 *            "longitude": (Number) The stored longitude.
 *        }
 * @param config: {
 *            "allowedRange": (Number) The max difference allowed in the two locations, before the penalty is assigned.
 *            "penaltyPoints": (Number) The number of penalty points.
*         }
 * @return ComparisonResult
 */
GeolocationComparator.compare = function (currentValue, storedValue, config) {
    if (logger.messageEnabled()) {
        logger.message("GeolocationComparator.compare:currentValue: " + JSON.stringify(currentValue));
        logger.message("GeolocationComparator.compare:storedValue: " + JSON.stringify(storedValue));
        logger.message("GeolocationComparator.compare:config: " + JSON.stringify(config));
    }

    // Check for undefined stored or current locations

    if (undefinedLocation(currentValue) && undefinedLocation(storedValue)) {
        return ComparisonResult.ZERO_PENALTY_POINTS;
    }
    if (undefinedLocation(currentValue) && !undefinedLocation(storedValue)) {
        return new ComparisonResult(config.penaltyPoints);
    }
    if (!undefinedLocation(currentValue) && undefinedLocation(storedValue)) {
        return new ComparisonResult(true);
    }

    // Both locations defined, therefore perform comparison

    var distance = calculateDistance(currentValue, storedValue);

    if (logger.messageEnabled()) {
        logger.message("Distance between (" + currentValue.latitude + "," + currentValue.longitude + ") and (" +
            storedValue.latitude + "," + storedValue.longitude + ") is " + distance + " miles");
    }

    if (parseFloat(distance.toPrecision(5)) === 0) {
        logger.message("Location is the same");
        return ComparisonResult.ZERO_PENALTY_POINTS;
    }

    if (distance <= config.allowedRange) {
        if (logger.messageEnabled()) {
            logger.message("Tolerated because distance not more then " + config.allowedRange);
        }
        return new ComparisonResult(true);
    } else {
        if (logger.messageEnabled()) {
            logger.message("Would be ignored if distance not more then " + config.allowedRange);
        }
        return new ComparisonResult(config.penaltyPoints);
    }
};


//---------------------------------------------------------------------------//
//                    Device Print Logic - DO NOT MODIFY                     //
//---------------------------------------------------------------------------//

// Utility functions

/**
 * Returns true if evaluating function f on each element of the Array a returns true.
 *
 * @param a: (Array) The array of elements to evaluate
 * @param f: (Function) A single argument function for mapping elements of the array to boolean.
 * @return boolean.
 */
all = function(a, f) {
    var i;
    for (i = 0; i < a.length; i++) {
        if (f(a[i]) === false) {
            return false;
        }
    }
    return true;
};

/**
 * Returns true if evaluating function f on any element of the Array a returns true.
 *
 * @param a: (Array) The array of elements to evaluate
 * @param f: (Function) A single argument function for mapping elements of the array to boolean.
 * @return boolean.
 */
any = function(a, f) {
    var i;
    for (i = 0; i < a.length; i++) {
        if (f(a[i]) === true) {
            return true;
        }
    }
    return false;
};

/**
 * Returns true if the provided location is null or has undefined longitude or latitude values.
 *
 * @param location: {
 *            "latitude": (Number) The latitude.
 *            "longitude": (Number) The longitude.
 *        }
 * @return boolean
 */
undefinedLocation = function(location) {
    return nullOrUndefined(location) || nullOrUndefined(location.latitude) || nullOrUndefined(location.longitude);
};

/**
 * Returns true if the provided value is null or undefined.
 *
 * @param value: a value of any type
 * @return boolean
 */
nullOrUndefined = function(value) {
    return value === null || value === undefined;
};

/**
 * Calculates the distances between the two locations.
 *
 * @param first: {
 *            "latitude": (Number) The first latitude.
 *            "longitude": (Number) The first longitude.
 *        }
 * @param second: {
 *            "latitude": (Number) The second latitude.
 *            "longitude": (Number) The second longitude.
 *        }
 * @return Number The distance between the two locations.
 */
calculateDistance = function(first, second) {
    var factor = (Math.PI / 180),
        theta,
        dist;
    function degreesToRadians(degrees) {
        return degrees * factor;
    }
    function radiansToDegrees(radians) {
        return radians / factor;
    }
    theta = first.longitude - second.longitude;
    dist = Math.sin(degreesToRadians(first.latitude)) * Math.sin(degreesToRadians(second.latitude))
        + Math.cos(degreesToRadians(first.latitude)) * Math.cos(degreesToRadians(second.latitude))
        * Math.cos(degreesToRadians(theta));
    dist = Math.acos(dist);
    dist = radiansToDegrees(dist);
    dist = dist * 60 * 1.1515;
    return dist;
};

/**
 * Converts a String holding a delimited sequence of values into an array.
 *
 * @param text (String) The String representation of a delimited sequence of values.
 * @param delimiter (String) The character delimiting values within the text String.
 * @return (Array) The comma separated values.
 */
splitAndTrim = function(text, delimiter) {

    var results = [],
        i,
        values,
        value;
    if (text === null) {
        return results;
    }

    values = text.split(delimiter);
    for (i = 0; i < values.length; i++) {
        value = values[i].trim();
        if (value !== "") {
            results.push(value);
        }
    }

    return results;
};

/**
 * Converts value to a percentage of range.
 *
 * @param value (Number) The actual number to be converted to a percentage.
 * @param range (Number) The total number of values (i.e. represents 100%).
 * @return (Number) The percentage.
 */
calculatePercentage = function(value, range) {
    if (range === 0) {
        return 0;
    }
    return parseFloat((value / range).toPrecision(2)) * 100;
};

/**
 * Creates a new array containing only those elements found in both arrays received as arguments.
 *
 * @param first (Array) The first array.
 * @param second (Array) The second array.
 * @return (Array) The elements that found in first and second.
 */
calculateIntersection = function(first, second) {
    return first.filter(function(element) {
        return second.indexOf(element) !== -1;
    });
};

function getValue(obj, attributePath) {
    var value = obj,
        i;
    for (i = 0; i < attributePath.length; i++) {
        if (value === undefined) {
            return null;
        }
        value = value[attributePath[i]];
    }
    return value;
}


function isLeafNode(attributeConfig) {
    return attributeConfig.comparator !== undefined;
}

function getAttributePaths(attributeConfig, attributePath) {

    var attributePaths = [],
        attributeName,
        attrPaths,
        attrPath,
        i;

    for (attributeName in attributeConfig) {
        if (attributeConfig.hasOwnProperty(attributeName)) {

            if (isLeafNode(attributeConfig[attributeName])) {
                attrPath = attributePath.slice();
                attrPath.push(attributeName);
                attributePaths.push(attrPath);
            } else {
                attrPath = attributePath.slice();
                attrPath.push(attributeName);
                attrPaths = getAttributePaths(attributeConfig[attributeName], attrPath);
                for (i = 0; i < attrPaths.length; i++) {
                    attributePaths.push(attrPaths[i]);
                }
            }
        }
    }

    return attributePaths;
}

function getDevicePrintAttributePaths(attributeConfig) {
    return getAttributePaths(attributeConfig, []);
}

function hasRequiredAttributes(devicePrint, attributeConfig) {

    var attributePaths = getDevicePrintAttributePaths(attributeConfig),
        i,
        attrValue,
        attrConfig;

    for (i = 0; i < attributePaths.length; i++) {

        attrValue = getValue(devicePrint, attributePaths[i]);
        attrConfig = getValue(attributeConfig, attributePaths[i]);

        if (attrConfig.required && attrValue === undefined) {
            logger.warning("Device Print profile missing required attribute, " + attributePaths[i]);
            return false;
        }
    }

    logger.message("device print has required attributes");
    return true;
}

function compareDevicePrintProfiles(attributeConfig, devicePrint, devicePrintProfiles, maxPenaltyPoints) {

    var attributePaths = getDevicePrintAttributePaths(attributeConfig),
        dao = sharedState.get('_DeviceIdDao'),
        results,
        j,
        aggregatedComparisonResult,
        i,
        currentValue,
        storedValue,
        attrConfig,
        comparisonResult,
        selectedComparisonResult,
        selectedProfile,
        curDevicePrintProfile,
        vals;

    results = [];
    for (j = 0; j < devicePrintProfiles.length; j++) {
        curDevicePrintProfile = JSON.parse(org.forgerock.json.JsonValue.json(devicePrintProfiles[j]));
        aggregatedComparisonResult = new ComparisonResult();
        for (i = 0; i < attributePaths.length; i++) {

            currentValue = getValue(devicePrint, attributePaths[i]);
            storedValue = getValue(curDevicePrintProfile.devicePrint, attributePaths[i]);
            attrConfig = getValue(attributeConfig, attributePaths[i]);

            if (storedValue === null) {
                comparisonResult = new ComparisonResult(attrConfig.penaltyPoints);
            } else {
                comparisonResult = attrConfig.comparator.compare(currentValue, storedValue, attrConfig.args);
            }

            if (logger.messageEnabled()) {
                logger.message("Comparing attribute path: " + attributePaths[i]
                    + ", Comparison result: successful=" + comparisonResult.isSuccessful() + ", penaltyPoints="
                    + comparisonResult.penaltyPoints + ", additionalInfoInCurrentValue="
                    + comparisonResult.additionalInfoInCurrentValue);
            }
            aggregatedComparisonResult.addComparisonResult(comparisonResult);
        }
        if (logger.messageEnabled()) {
            logger.message("Aggregated comparison result: successful="
                + aggregatedComparisonResult.isSuccessful() + ", penaltyPoints="
                + aggregatedComparisonResult.penaltyPoints + ", additionalInfoInCurrentValue="
                + aggregatedComparisonResult.additionalInfoInCurrentValue);
        }

        results.push({
            key: aggregatedComparisonResult,
            value: devicePrintProfiles[j]
        });
    }

    if (results.length === 0) {
        return null;
    }

    results.sort(function(a, b) {
        return ComparisonResult.compare(a.key, b.key);
    });
    selectedComparisonResult = results[0].key;
    if (logger.messageEnabled()) {
        logger.message("Selected comparison result: successful=" + selectedComparisonResult.isSuccessful()
            + ", penaltyPoints=" + selectedComparisonResult.penaltyPoints + ", additionalInfoInCurrentValue="
            + selectedComparisonResult.additionalInfoInCurrentValue);
    }

    selectedProfile = null;
    if (selectedComparisonResult.penaltyPoints <= maxPenaltyPoints) {
        selectedProfile = results[0].value;
        if (logger.messageEnabled()) {
            logger.message("Selected profile: " + selectedProfile +
                " with " + selectedComparisonResult.penaltyPoints + " penalty points");
        }
    }

    if (selectedProfile === null) {
        return false;
    }

    /* update profile */
    selectedProfile.put("selectionCounter",
        java.lang.Integer.valueOf(parseInt(selectedProfile.get("selectionCounter"), 10) + 1));
    selectedProfile.put("lastSelectedDate", java.lang.Long.valueOf(new Date().getTime()));
    selectedProfile.put("devicePrint", devicePrint);

    vals = [];
    for (i = 0; i < devicePrintProfiles.length; i++) {
        vals.push(org.forgerock.json.JsonValue.json(devicePrintProfiles[i]));
    }

    dao.saveDeviceProfiles(username, realm, vals);

    return true;
}

function matchDevicePrint() {

    if (!username) {
        logger.error("Username not set. Cannot compare user's device print profiles.");
        authState = FAILED;
    } else {

        if (logger.messageEnabled()) {
            logger.message("client devicePrint: " + clientScriptOutputData);
        }

        var getProfiles = function () {

                function isExpiredProfile(devicePrintProfile) {
                    var expirationDate = new Date(),
                        lastSelectedDate;
                    expirationDate.setDate(expirationDate.getDate() - config.profileExpiration);

                    lastSelectedDate = new Date(devicePrintProfile.lastSelectedDate);

                    return lastSelectedDate < expirationDate;
                }

                function getNotExpiredProfiles() {
                    var profile,
                        dao = sharedState.get('_DeviceIdDao'),
                        results = [],
                        profiles,
                        iter;

                    profiles = dao.getDeviceProfiles(username, realm);

                    if (profiles) {
                        iter = profiles.iterator();

                        while (iter.hasNext()) {
                            profile = iter.next().getObject();
                            if (!isExpiredProfile(profile)) {
                                results.push(profile);
                            }
                        }
                    }
                    if (logger.messageEnabled()) {
                        logger.message("stored non-expired profiles: " + results);
                    }
                    return results;
                }

                return getNotExpiredProfiles();
            },
            devicePrint = JSON.parse(clientScriptOutputData),
            devicePrintProfiles = getProfiles();

        if (!hasRequiredAttributes(devicePrint, config.attributes)) {
            logger.message("devicePrint.hasRequiredAttributes: false");
            // Will fail this module but fall-through to next module. Which should be OTP.
            authState = FAILED;
        } else if (compareDevicePrintProfiles(config.attributes, devicePrint, devicePrintProfiles, config.maxPenaltyPoints)) {
            logger.message("devicePrint.hasValidProfile: true");
            authState = SUCCESS;
        } else {
            logger.message("devicePrint.hasValidProfile: false");
            sharedState.put('devicePrintProfile', JSON.stringify(devicePrint));
            // Will fail this module but fall-through to next module. Which should be OTP.
            authState = FAILED;
        }
    }
}

matchDevicePrint();
javascript

Open deviceIdMatch-server-side.js in your browser.

deviceProfileMatch-decision-node.js

View script
/*
 * Copyright 2020-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/** ******************************************************************
 *
 * The following script is a simplified template for understanding
 * the basics of device matching. _This is not functionally complete._
 * For a functionally complete script as well as a development toolkit,
 * visit https://github.com/ForgeRock/forgerock-device-match-script.
 *
 * Global node variables accessible within this scope:
 * 1. `sharedState` provides access to incoming request
 * 2. `deviceProfilesDao` provides access to stored profiles
 * 3. `outcome` variable maps to auth tree node outcomes; values are
 *    'true', 'false', or 'unknownDevice' (notice _all_ are strings).
 * ******************************************************************/

/**
 * Get the incoming request's device profile.
 * Returns serialized JSON (type string); parsing this will result a
 * native JS object.
 */
var incomingJson = sharedState.get('forgeRock.device.profile').toString();
var incoming = JSON.parse(incomingJson);

/**
 * Get the incoming user's username and realm.
 * Notice the use of `.asString()`.
 */
var username = sharedState.get("username").asString();
var realm = sharedState.get("realm").asString();

/**
 * Get the user's stored profiles for appropriate realm.
 * Returns a _special_ object with methods for profile data
 */
var storedProfiles = deviceProfilesDao.getDeviceProfiles(username, realm);

// Default to `outcome` of 'unknownDevice'
outcome = 'unknownDevice';

if (storedProfiles) {
    var i = 0;
    // NOTE: `.size()` method returns the number of stored profiles
    var len = storedProfiles.size();

    for (i; i < len; i++) {
        /**
         * Get the stored profile.
         * Returns serialized JSON (type string); parsing this will result
         * a native JS object.
         */
        var storedJson = storedProfiles.get(i);
        var stored = JSON.parse(storedJson);

        /**
         * Find a stored profile with the same identifier.
         */
        if (incoming.identifier === stored.identifier) {

            /**
             * Now that you've found the appropriate profile, you will perform
             * the logic here to match the values of the `incoming` profile
             * with that of the `stored` profile.
             *
             * The result of the matching logic is assigned to `outcome`. Since
             * we have profiles of the same identifier, the value (type string)
             * should now be either 'true' or 'false' (properties matched or not).
             *
             * For more information about this topic, visit this Github repo:
             * https://github.com/ForgeRock/forgerock-device-match-script
             */
            outcome = 'false';
        }
    }
}
javascript

Open deviceProfileMatch-decision-node.js in your browser.

facebook-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('id'));
    normalizedProfileData.put('displayName', rawProfile.get('name'));
    normalizedProfileData.put('givenName', rawProfile.get('first_name'));
    normalizedProfileData.put('familyName', rawProfile.get('last_name'));
    normalizedProfileData.put('photoUrl', rawProfile.get('picture').get('data').get('url'));
    normalizedProfileData.put('email', rawProfile.get('email'));
    normalizedProfileData.put('username', rawProfile.get('email'));

    return normalizedProfileData;
}());
javascript

Open facebook-profile-normalization.js in your browser.

fontdetector.js

View script
var fontDetector = (function () {
    /**
     * JavaScript code to detect available availability of a
     * particular font in a browser using JavaScript and CSS.
     *
     * Author : Lalit Patel
     * Website: http://www.lalit.org/lab/javascript-css-font-detect/
     * License: Apache Software License 2.0
     *          http://www.apache.org/licenses/LICENSE-2.0
     * Version: 0.15 (21 Sep 2009)
     *          Changed comparision font to default from sans-default-default,
     *          as in FF3.0 font of child element didn't fallback
     *          to parent element if the font is missing.
     * Version: 0.2 (04 Mar 2012)
     *          Comparing font against all the 3 generic font families ie,
     *          'monospace', 'sans-serif' and 'sans'. If it doesn't match all 3
     *          then that font is 100% not available in the system
     * Version: 0.3 (24 Mar 2012)
     *          Replaced sans with serif in the list of baseFonts
     */
    /*
     * Portions Copyrighted 2013-2025 Ping Identity Corporation.
     */
    var detector = {}, baseFonts, testString, testSize, h, s, defaultWidth = {}, defaultHeight = {}, index;

    // a font will be compared against all the three default fonts.
    // and if it doesn't match all 3 then that font is not available.
    baseFonts = ['monospace', 'sans-serif', 'serif'];

    //we use m or w because these two characters take up the maximum width.
    // And we use a LLi so that the same matching fonts can get separated
    testString = "mmmmmmmmmmlli";

    //we test using 72px font size, we may use any size. I guess larger the better.
    testSize = '72px';

    h = document.getElementsByTagName("body")[0];

    // create a SPAN in the document to get the width of the text we use to test
    s = document.createElement("span");
    s.style.fontSize = testSize;
    s.innerHTML = testString;
    for (index in baseFonts) {
        //get the default width for the three base fonts
        s.style.fontFamily = baseFonts[index];
        h.appendChild(s);
        defaultWidth[baseFonts[index]] = s.offsetWidth; //width for the default font
        defaultHeight[baseFonts[index]] = s.offsetHeight; //height for the defualt font
        h.removeChild(s);
    }

    detector.detect = function(font) {
        var detected = false, index, matched;
        for (index in baseFonts) {
            s.style.fontFamily = font + ',' + baseFonts[index]; // name of the font along with the base font for fallback.
            h.appendChild(s);
            matched = (s.offsetWidth !== defaultWidth[baseFonts[index]] || s.offsetHeight !== defaultHeight[baseFonts[index]]);
            h.removeChild(s);
            detected = detected || matched;
        }
        return detected;
    };

    return detector;
}());
javascript

Open fontdetector.js in your browser.

github-profile-normalization.js

View script
/*
 * Copyright 2022-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7.2/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('id'));
    normalizedProfileData.put('displayName', rawProfile.get('name'));
    normalizedProfileData.put('username', rawProfile.get('login'));

    return normalizedProfileData;
}());
javascript

Open github-profile-normalization.js in your browser.

google-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - JsonValue (1).
 *              The social identity provider profile information for the authenticating user.
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('sub'));
    normalizedProfileData.put('displayName', rawProfile.get('name'));
    normalizedProfileData.put('givenName', rawProfile.get('given_name'));
    normalizedProfileData.put('familyName', rawProfile.get('family_name'));
    normalizedProfileData.put('photoUrl', rawProfile.get('picture'));
    normalizedProfileData.put('email', rawProfile.get('email'));
    normalizedProfileData.put('username', rawProfile.get('email'));
    normalizedProfileData.put('locale', rawProfile.get('locale'));

    return normalizedProfileData;
}());
javascript

Open google-profile-normalization.js in your browser.

instagram-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('id'));
    normalizedProfileData.put('username', rawProfile.get('username'));

    return normalizedProfileData;
}());
javascript

Open instagram-profile-normalization.js in your browser.

itsme-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('sub'));
    normalizedProfileData.put('displayName', rawProfile.get('name'));
    normalizedProfileData.put('givenName', rawProfile.get('given_name'));
    normalizedProfileData.put('familyName', rawProfile.get('family_name'));
    normalizedProfileData.put('username', rawProfile.get('email'));
    normalizedProfileData.put('email', rawProfile.get('email'));

    return normalizedProfileData;
}());
javascript

Open itsme-profile-normalization.js in your browser.

line-profile-normalization.js

View script
/*
 * Copyright 2024-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var normalizedProfileData = new JavaImporter(
        org.forgerock.json.JsonValue
    ).JsonValue.json(new JavaImporter(
        org.forgerock.json.JsonValue
    ).JsonValue.object());

    var email = null;
    var username = null;
    var firstName = null;
    var lastName = null;

    if (sharedState.get('claims_set') !== null && "email" in sharedState.get('claims_set')) {
        email = sharedState.get('claims_set').email;
        username = email;
    } else {
        // Ensure that your LINE provider is configured to provide users' email addresses
        throw new Error("Email is required");
    }

    if (rawProfile.isDefined('name')) {
        var nameParts = rawProfile.get('name').asString().split(' ');
        firstName = nameParts[0];
        lastName = nameParts[nameParts.length - 1];
    }

    normalizedProfileData.put('id', rawProfile.get('sub'));
    normalizedProfileData.put("displayName", rawProfile.get("name"));
    normalizedProfileData.put('photoUrl', rawProfile.get('picture'));
    normalizedProfileData.put('email', email);
    if (firstName !== null) {
        normalizedProfileData.put('givenName', firstName);
    }
    if (lastName !== null) {
        normalizedProfileData.put('familyName', lastName);
    }
    normalizedProfileData.put('username', username);

    return normalizedProfileData;
}());
javascript

Open line-profile-normalization.js^ in your browser.

linkedIn-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('id'));
    normalizedProfileData.put('givenName', rawProfile.get('firstName').get('localized').get(0));
    normalizedProfileData.put('familyName', rawProfile.get('lastName').get('localized').get(0));
    normalizedProfileData.put('photoUrl', rawProfile.get('profilePicture').get('displayImage'));
    normalizedProfileData.put('email', rawProfile.get('elements').get(0).get('handle~').get('emailAddress'));
    normalizedProfileData.put('username', rawProfile.get('elements').get(0).get('handle~').get('emailAddress'));

    return normalizedProfileData;
}());
javascript

Open linkedIn-profile-normalization.js in your browser.

linkedIn-v2-profile-normalization.js

View script
/*
 * Copyright 2024-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var normalizedProfileData = new JavaImporter(
        org.forgerock.json.JsonValue
    ).JsonValue.json(new JavaImporter(
        org.forgerock.json.JsonValue
    ).JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('sub'));
    normalizedProfileData.put("displayName", rawProfile.get("name"));
    normalizedProfileData.put('givenName', rawProfile.get('given_name'));
    normalizedProfileData.put('familyName', rawProfile.get('family_name'));
    normalizedProfileData.put('photoUrl', rawProfile.get('picture'));
    normalizedProfileData.put('email', rawProfile.get('email'));
    normalizedProfileData.put('emailVerified', rawProfile.get('email_verified'));
    normalizedProfileData.put('username', rawProfile.get('email'));

    return normalizedProfileData;
}());
javascript

Open linkedIn-v2-profile-normalization.js in your browser.

microsoft-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('id'));
    normalizedProfileData.put('displayName', rawProfile.get('displayName'));
    normalizedProfileData.put('givenName', rawProfile.get('givenName'));
    normalizedProfileData.put('familyName', rawProfile.get('surname'));
    normalizedProfileData.put('email', rawProfile.get('userPrincipalName'));
    normalizedProfileData.put('username', rawProfile.get('userPrincipalName'));

    return normalizedProfileData;
}());
javascript

Open microsoft-profile-normalization.js in your browser.

normalized-profile-to-identity.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script translates the normalized social identity profile information for the authenticating user
 * into the identity object key/value pairs.
 *
 * Defined variables:
 * normalizedProfile - The social identity provider profile information for the authenticating user
 *                     in a standard format expected by this node.
 *                     JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var identityData = frJava.JsonValue.json(frJava.JsonValue.object());

    identityData.put('givenName', normalizedProfile.get('givenName'));
    identityData.put('sn', normalizedProfile.get('familyName'));
    identityData.put('mail', normalizedProfile.get('email'));
    identityData.put('cn', normalizedProfile.get('displayName'));
    identityData.put('userName', normalizedProfile.get('username'));
    identityData.put('iplanet-am-user-alias-list', selectedIdp + '-' + normalizedProfile.get('id').asString());

    return identityData;
}());
javascript

Open normalized-profile-to-identity.js in your browser.

normalized-profile-to-managed-user.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script translates the normalized social identity profile information for the authenticating user
 * into the managed user object key/value pairs.
 *
 * Defined variables:
 * normalizedProfile - The social identity provider profile information for the authenticating user
 *                     in a standard format expected by this node.
 *                     JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var managedUserData = frJava.JsonValue.json(frJava.JsonValue.object());

    managedUserData.put('givenName', normalizedProfile.get('givenName'));
    managedUserData.put('sn', normalizedProfile.get('familyName'));
    managedUserData.put('mail', normalizedProfile.get('email'));
    managedUserData.put('userName', normalizedProfile.get('username'));

    if (normalizedProfile.get('postalAddress').isNotNull()) {
        managedUserData.put('postalAddress', normalizedProfile.get('postalAddress'));
    }
    if (normalizedProfile.get('addressLocality').isNotNull()) {
        managedUserData.put('city', normalizedProfile.get('addressLocality'));
    }
    if (normalizedProfile.get('addressRegion').isNotNull()) {
        managedUserData.put('stateProvince', normalizedProfile.get('addressRegion'));
    }
    if (normalizedProfile.get('postalCode').isNotNull()) {
        managedUserData.put('postalCode', normalizedProfile.get('postalCode'));
    }
    if (normalizedProfile.get('country').isNotNull()) {
        managedUserData.put('country', normalizedProfile.get('country'));
    }
    if (normalizedProfile.get('phone').isNotNull()) {
        managedUserData.put('telephoneNumber', normalizedProfile.get('phone'));
    }

    // if the givenName and familyName is null or empty
    // then add a boolean flag to the shared state to indicate names are not present
    // this could be used elsewhere
    // for eg. this could be used in a scripted decision node to by-pass patching
    // the user object with blank values when givenName  and familyName is not present
     var noGivenName = normalizedProfile.get('givenName').isNull()
                                      || normalizedProfile.get('givenName').asString().trim().length === 0
     var noFamilyName = normalizedProfile.get('familyName').isNull()
                                       || normalizedProfile.get('familyName').asString().trim().length === 0
     sharedState.put('nameEmptyOrNull', noGivenName && noFamilyName)

    return managedUserData;
}());
javascript

oauth2-access-token-modification.js

View script
/*
 * Copyright 2019-2025 Ping Identity Corporation. All Rights Reserved.
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script lets you modify information associated with an OAuth2 access token
 * with methods provided by the AccessToken (1) interface.
 * The changes made to OAuth2 access tokens will directly impact the size of the CTS tokens,
 * and, similarly, the size of the JWTs if client-based OAuth2 tokens are utilized.
 * When adding/updating fields make sure that the token size remains within client/user-agent limits.
 *
 * Defined variables:
 * accessToken - AccessToken (1).
 *               The access token to be updated.
 *               Mutable object, all changes to the access token will be reflected.
 * scopes - Set<String> (6).
 *          Always present, the requested scopes.
 * requestProperties - Unmodifiable Map (5).
 *                     Always present, contains a map of request properties:
 *                     requestUri - The request URI.
 *                     realm - The realm that the request relates to.
 *                     requestParams - A map of the request params and/or posted data.
 *                                     Each value is a list of one or more properties.
 *                                     Please note that these should be handled in accordance with OWASP best practices:
 *                                     https://owasp.org/www-community/vulnerabilities/Unsafe_use_of_Reflection.
 * clientProperties - Unmodifiable Map (5).
 *                    Present if the client specified in the request was identified, contains a map of client properties:
 *                    clientId - The client's URI for the request locale.
 *                    allowedGrantTypes - List of the allowed grant types (org.forgerock.oauth2.core.GrantType) for the client.
 *                    allowedResponseTypes - List of the allowed response types for the client.
 *                    allowedScopes - List of the allowed scopes for the client.
 *                    customProperties - A map of the custom properties of the client.
 *                                       Lists or maps will be included as sub-maps; for example:
 *                                       customMap[Key1]=Value1 will be returned as customMap -> Key1 -> Value1.
 *                                       To add custom properties to a client, update the Custom Properties field
 *                                       in AM Console > Realm Name > Applications > OAuth 2.0 > Clients > Client ID > Advanced.
 * identity - AMIdentity (3).
 *            Always present, the identity of the resource owner.
 * session - SSOToken (4).
 *           Present if the request contains the session cookie, the user's session object.
 * scriptName - String (primitive).
 *              Always present, the display name of the script.
 * logger - Always present, the "OAuth2Provider" debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 *          Corresponding log files will be prefixed with: scripts.OAUTH2_ACCESS_TOKEN_MODIFICATION.
 * httpClient - HTTP Client (8).
 *              Always present, the HTTP Client instance:
 *              https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-http-client.html#scripting-api-global-http-client.
 *
 * Return - no value is expected, changes shall be made to the accessToken parameter directly.
 *
 * Class reference:
 * (1) AccessToken - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/oauth2/core/AccessToken.html.
 * (3) AMIdentity - https://backstage.forgerock.com/docs/am/7/apidocs/com/sun/identity/idm/AMIdentity.html.
 * (4) SSOToken - https://backstage.forgerock.com/docs/am/7/apidocs/com/iplanet/sso/SSOToken.html.
 * (5) Map - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashMap.html,
 *           or https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 * (6) Set - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashSet.html.
 * (8) Client - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/http/Client.html.
 */

/* EXAMPLE
(function () {
    var frJava = JavaImporter(
        org.forgerock.http.protocol.Request,
        org.forgerock.http.protocol.Response
    );

    // Always includes this field in the token.
    accessToken.setField('key1', 'value1');

    // Receives and adds to the access token additional values by performing a REST call to an external service.
    // WARNING: Below, you will find a reference to a third-party site, which is provided only as an example.
    var uri = 'https://jsonplaceholder.typicode.com/posts';

    try {
        var request = new frJava.Request();

        // You can chain methods that return the request object.
        request.setUri(uri)
            .setMethod('POST')
            .setEntity(JSON.stringify({
                updatedFields: {
                    key2: 'value2',
                    key3: 'value3'
                }
            }));

        // You can call a method when chaining is not possible.
        request.getHeaders().add('Content-Type', 'application/json; charset=UTF-8');

        // Sends the request and receives the response.
        var response = httpClient.send(request).getOrThrow();

        // Checks if the response status is as expected.
        if (response.getStatus() === org.forgerock.http.protocol.Status.CREATED) {
            var result = JSON.parse(response.getEntity().getString());

            // Set multiple token fields at once.
            accessToken.setFields(result.updatedFields);
        } else {
            logger.error('Unable to obtain access token modifications. Status: ' + response.getStatus() + '. Content: ' + response.getEntity().getString());
        }
    } catch (e) {
        logger.error('The request processing was interrupted. ' + e);

        // The access token request fails with the HTTP 500 error in this case.
        throw ('Unable to obtain response from: ' + uri);
    }

    // Adds new fields containing identity attribute values to the access token.
    accessToken.setField('mail', identity.getAttribute('mail'));
    accessToken.setField('phone', identity.getAttribute('telephoneNumber').toArray()[0]);

    // Adds new fields containing the session property values.
    // NOTE: session may not be available for non-interactive authorization grants.
    if (session) {
        try {
            accessToken.setField('ipAddress', session.getProperty('Host'));
        } catch (e) {
            logger.error('Unable to retrieve session property value. ' + e);
        }
    }

    // Removes a native field from the token entry, that was set by AM.
    // WARNING: removing native fields from the token may result in loss of functionality.
    // accessToken.removeTokenName()

    // No return value is expected. Let it be undefined.
}());
*/
javascript

Open oauth2-access-token-modification.js in your browser.

oauth2-authorize-endpoint-data-provider.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script lets you return additional data when authorize request is called.
 *
 * Defined variables:
 *
 * session - SSOToken (1)
 *           Present if the request contains the session cookie, the user's session object.
 *
 * httpClient - HTTP Client (2).
 *              Always present, the HTTP client that can be used to make external HTTP requests
 *
 * logger - Debug (3)
 *          Always present, the "ScriptedAuthorizeEndpointDataProvider" debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 *          Corresponding log files will be prefixed with: scripts.OAUTH2_AUTHORIZE_ENDPOINT_DATA_PROVIDER.
 *
 * scriptName - String (primitive).
 *              Always present, the display name of the script
 *
 * Return - a Map<String, String> of additional data (4).
 *
 * Class reference:
 * (1) SSOToken - https://backstage.forgerock.com/docs/am/7/apidocs/com/iplanet/sso/SSOToken.html.
 * (2) Client - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/http/Client.html.
 * (3) Debug - https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * (4) Map - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashMap.html.
 */

/**
 * Default authorize endpoint data provider script to use as a template for new scripts.
 */

/* EXAMPLE
var map = new java.util.HashMap();

function addAdditionalData() {

    //If constant data needs to be returned
    map.put("hello", "world");

    //If some data needs to be returned from third party service
    addAdditionalDataFromExternalService();

    //If there is a need to return some user session data
    addAdditionalDataFromSessionProperties()

    return map;
};

function addAdditionalDataFromExternalService() {
  var frJava = JavaImporter(
        org.forgerock.oauth2.core.exceptions.ServerException
    );
  try {
        //Obtain additional data by performing a REST call to an external service
        var request = new org.forgerock.http.protocol.Request();
        request.setUri("https://third.party.app/hello.jsp");
        request.setMethod("POST");
        //request.setEntity("foo=bar&hello=world");
        request.setEntity(json(object(
                    field("foo", "bar"))));
        var response = httpClient.send(request).getOrThrow();
        logResponse(response);
        var result = JSON.parse(response.getEntity().getString());
        map.put("someKey",result.get("someKey"));
  } catch (err) {
     throw new frJava.ServerException(err);
  }
};

function addAdditionalDataFromSessionProperties() {
  //Add additional data from session property values
   if (session != null) { // session is not available for resource owner password credentials grant
     map.put("ipAddress", session.getProperty("Host"))
   }
};

function logResponse(response) {
    logger.message("User REST Call. Status: " + response.getStatus() + ", Body: " + response.getEntity().getString());
};

addAdditionalData();
*/
javascript

oauth2-evaluate-scope.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script lets you populate the scopes with profile attribute values when the tokeninfo endpoint is called.
 * For example, if one of the scopes is mail, AM sets mail to the resource owner's email address in the token information returned.
 *
 * Defined variables:
 * accessToken - AccessToken (1).
 *               The access token to be updated.
 *               Mutable object, all changes to the access token will be reflected.
 * identity - AMIdentity (2).
 *            The client's identity if present or the resource owner's identity. Can be null.
 * scriptName - String (primitive).
 *              Always present, the display name of the script.
 * logger - Always present, the debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 *          Corresponding log files will be prefixed with: scripts.OAUTH2_EVALUATE_SCOPE
 * httpClient - HTTP Client (3).
 *              Always present, the HTTP Client instance:
 *              https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-http-client.html#scripting-api-global-http-client.
 *
 * Return - a Map<String, Object> of the access token's information (4).
 *
 * Class reference:
 * (1) AccessToken - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/oauth2/core/AccessToken.html.
 * (2) AMIdentity - https://backstage.forgerock.com/docs/am/7/apidocs/com/sun/identity/idm/AMIdentity.html.
 * (3) Client - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/http/Client.html.
 * (4) Map - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashMap.html.
 */

/**
 * Default evaluate scope script to use as a template for new scripts.
 */

(function () {
    var map = new java.util.HashMap();
    if (identity !== null) {
        var scopes = accessToken.getScope().toArray();
        scopes.forEach(function (scope) {
            var attributes = identity.getAttribute(scope).toArray();
            map.put(scope, attributes.join(","));
        });
    } else {
        logger.error('identity is null');
    }
    return map;
}());
javascript

Open oauth2-evaluate-scope.js in your browser.

oauth2-may-act.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script lets you add may_act field
 * to an OAuth2 access token
 * or OIDC ID Token
 * object with the setMayAct method.
 *
 * Defined variables:
 * token - AccessToken (1) or org.forgerock.openidconnect.OpenIdConnectToken.
 *               The token to be updated.
 *               Mutable object, all changes to the token will be reflected.
 * scopes - Set<String> (6).
 *          Always present, the requested scopes.
 * requestProperties - Unmodifiable Map (5).
 *                     Always present, contains a map of request properties:
 *                     requestUri - The request URI.
 *                     realm - The realm that the request relates to.
 *                     requestParams - A map of the request params and/or posted data.
 *                                     Each value is a list of one or more properties.
 *                                     Please note that these should be handled in accordance with OWASP best practices:
 *                                     https://owasp.org/www-community/vulnerabilities/Unsafe_use_of_Reflection.
 * clientProperties - Unmodifiable Map (5).
 *                    Present if the client specified in the request was identified, contains a map of client properties:
 *                    clientId - The client's URI for the request locale.
 *                    allowedGrantTypes - List of the allowed grant types (org.forgerock.oauth2.core.GrantType) for the client.
 *                    allowedResponseTypes - List of the allowed response types for the client.
 *                    allowedScopes - List of the allowed scopes for the client.
 *                    customProperties - A map of the custom properties of the client.
 *                                       Lists or maps will be included as sub-maps; for example:
 *                                       customMap[Key1]=Value1 will be returned as customMap -> Key1 -> Value1.
 *                                       To add custom properties to a client, update the Custom Properties field
 *                                       in AM Console > Realm Name > Applications > OAuth 2.0 > Clients > Client ID > Advanced.
 * identity - AMIdentity (3).
 *            Always present, the identity of the resource owner.
 * session - SSOToken (4).
 *           Present if the request contains the session cookie, the user's session object.
 * scriptName - String (primitive).
 *              Always present, the display name of the script.
 * logger - Always present, the "OAuth2Provider" debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 *          Corresponding log files will be prefixed with: scripts.OAUTH2_MAY_ACT.
 *
 * Return - no value is expected, changes shall be made to the token parameter directly.
 *
 * Class reference:
 * (1) AccessToken - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/oauth2/core/AccessToken.html.
 * (3) AMIdentity - https://backstage.forgerock.com/docs/am/7/apidocs/com/sun/identity/idm/AMIdentity.html.
 * (4) SSOToken - https://backstage.forgerock.com/docs/am/7/apidocs/com/iplanet/sso/SSOToken.html.
 * (5) Map - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashMap.html,
 *           or https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 * (6) Set - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashSet.html.
 */

/* EXAMPLE
(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var mayAct = frJava.JsonValue.json(frJava.JsonValue.object());
    mayAct.put('client_id', 'myClient');
    mayAct.put('sub', '(usr!myActor)');

    token.setMayAct(mayAct);

    // No return value is expected. Let it be undefined.
}());
*/
javascript

Open oauth2-may-act.js in your browser.

oauth2-scripted-jwt-issuer.js

View script
/*
 * Copyright 2022-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script lets you to derive the configuration for a dynamic JWT issuer from the issuer string.
 * A JWT issuer is made up of the following:
 *   - issuer - the identifier of the entity that issues JWTs
 *   - resource owner subject claim - the name of the claim in the JWT that identifies the resource owner
 *   - consented scope claim - the name of the claim in the JWT that represents scope that the resource owner
 *                             has already consented to externally
 *   - authorized subjects - the set of principal identifiers that are authorized to be used as resource owners
 *                           by the issuer
 *   - JWKs - either a set of JWKs or connection details for obtaining that set, that are the public keys that
 *            can verify the signature on the issued JWTs.
 *
 * Defined variables:
 * issuer - String
 *          The issuer from the bearer JWT.
 * realm - String
 *         The path of the realm that is handling the request.
 * scriptName - String.
 *              Always present, the display name of the script.
 * logger - Always present, the script debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 *          Corresponding log files will be prefixed with: scripts.OAUTH2_SCRIPTED_JWT_ISSUER.
 * httpClient - HTTP Client (1).
 *              Always present, the HTTP Client instance:
 *              https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-http-client.html#scripting-api-global-http-client.
 * idRepository - Identity Repository (2). Always present.
 * secrets - Secrets accessor (3). Always present.
 *
 * Return - org.forgerock.oauth2.core.TrustedJwtIssuerConfig (4) - the configuration of the trusted JWT issuer.
 *
 * Class reference:
 * (1) Client - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/http/Client.html.
 * (2) ScriptedIdentityRepository - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/openam/scripting/api/identity/ScriptedIdentityRepository.html.
 * (3) ScriptedSecrets - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/openam/scripting/api/secrets/ScriptedSecrets.html.
 * (4) TrustedJwtIssuerConfig - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/oauth2/core/TrustedJwtIssuerConfig.html.
 */

/* EXAMPLE
(function () {
    var frJava = JavaImporter(
        org.forgerock.oauth2.core.TrustedJwtIssuerConfig,
        java.util.Collections
    );

    var iss = idRepository.getIdentity(issuer);
    if (iss == null) {
        logger.message('No issuer found for: '+issuer);
        return null;
    }
    logger.message('Found issuer: '+iss);
    // in this example either a JWK set or a URI to a JWK set are in the postalAddress attribute
    var jwksAttrs = iss.getAttributeValues('postalAddress');
    var jwkSet = jwksAttrs.length === 0 ? null : jwksAttrs[0];
    var config = new frJava.TrustedJwtIssuerConfig(
        issuer,
        'sub',
        'scope',
        // in this example, valid subjects are stored in the mail attribute
        iss.getAttributeValues('mail'),
        jwkSet.startsWith('{') ? jwkSet : null,
        jwkSet.startsWith('http') ? jwkSet : null,
        '5 minutes',
        '1 minute'
    );
    return config;
}());
*/
javascript

Open oauth2-scripted-jwt-issuer.js in your browser.

oauth2-validate-scope.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script validates the requested scopes against the allowed scopes.
 * If no scopes are requested, default scopes are assumed.
 * The script has four top level functions that could be executed during the different OAuth2 flows:
 *      - validateAuthorizationScope
 *      - validateAccessTokenScope
 *      - validateRefreshTokenScope
 *      - validateBackChannelAuthorizationScope
 *
 * Defined variables:
 * requestedScopes - Set<String> (1).
 *          The set of requested scopes.
 * defaultScopes - Set<String> (1).
 *                 The set of default scopes.
 * allowedScopes - Set<String> (1).
 *                 The set of allowed scopes.
 * scriptName - String (primitive).
 *              Always present, the display name of the script.
 * logger - Always present, the debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 *          Corresponding log files will be prefixed with: scripts.OAUTH2_VALIDATE_SCOPE
 * httpClient - HTTP Client (2).
 *              Always present, the HTTP Client instance:
 *              https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-http-client.html#scripting-api-global-http-client.
 *
 * Throws InvalidScopeException:
 *      - if there are no scopes requested and default scopes are empty
 *      - if a requested scope is not allowed
 *
 * Return - a Set<String> of validated scopes (1).
 *
 * Class reference:
 * (1) Set - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashSet.html.
 * (2) Client - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/http/Client.html.
 */

/**
 * Default validate scope script.
 */
function validateScopes () {
    var frJava = JavaImporter(
        org.forgerock.oauth2.core.exceptions.InvalidScopeException
    );

    var scopes;
    if (requestedScopes == null || requestedScopes.isEmpty()) {
        scopes = defaultScopes;
    } else {
        scopes = new java.util.HashSet(allowedScopes);
        scopes.retainAll(requestedScopes);
        if (requestedScopes.size() > scopes.size()) {
            var invalidScopes = new java.util.HashSet(requestedScopes);
            invalidScopes.removeAll(allowedScopes);
            throw new frJava.InvalidScopeException('Unknown/invalid scope(s)');
        }
    }

    if (scopes == null || scopes.isEmpty()) {
        throw new frJava.InvalidScopeException('No scope requested and no default scope configured');
    }
    return scopes;
}

function validateAuthorizationScope () {
    return validateScopes();
}

function validateAccessTokenScope () {
    return validateScopes();
}

function validateRefreshTokenScope () {
    return validateScopes();
}

function validateBackChannelAuthorizationScope () {
    return validateScopes();
}
javascript

Open oauth2-validate-scope.js in your browser.

oidc-claims-extension.js

View script
/*
 * Copyright 2014-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script computes claim values returned in ID tokens and/or at the UserInfo Endpoint.
 * The claim values are computed for:
 * the claims derived from the requested scopes,
 * the claims provided by the authorization server,
 * and the claims requested by the client via the claims parameter.
 *
 * In the CONFIGURATION AND CUSTOMIZATION section, you can
 * define the scope-to-claims mapping, and
 * assign to each claim a resolver function that will compute the claim value.
 *
 * Defined variables (class references are provided below):
 * scopes - Set<String> (6).
 *          Always present, the requested scopes.
 * claims - Map<String, Object> (5).
 *          Always present, default server provided claims.
 * claimObjects - List<Claim> (7, 2).
 *                Always present, the default server provided claims.
 * requestedClaims - Map<String, Set<String>> (5).
 *                   Always present, not empty if the request contains the claims parameter and the server has enabled
 *                   claims_parameter_supported. A map of the requested claims to possible values, otherwise empty;
 *                   requested claims with no requested values will have a key but no value in the map. A key with
 *                   a single value in its Set (6) indicates that this is the only value that should be returned.
 * requestedTypedClaims - List<Claim> (7, 2).
 *                        Always present, the requested claims.
 *                        Requested claims with no requested values will have a claim with no values.
 *                        A claim with a single value indicates this is the only value that should be returned.
 * claimsLocales - List<String> (7).
 *                 The values from the 'claims_locales' parameter.
 *                 See https://openid.net/specs/openid-connect-core-1_0.html#ClaimsLanguagesAndScripts for the OIDC specification details.
 * requestProperties - Unmodifiable Map (5).
 *                     Always present, contains a map of request properties:
 *                     requestUri - The request URI.
 *                     realm - The realm that the request relates to.
 *                     requestParams - A map of the request params and/or posted data.
 *                                     Each value is a list of one or more properties.
 *                                     Please note that these should be handled in accordance with OWASP best practices:
 *                                     https://owasp.org/www-community/vulnerabilities/Unsafe_use_of_Reflection.
 * clientProperties - Unmodifiable Map (5).
 *                    Present if the client specified in the request was identified, contains a map of client properties:
 *                    clientId - The client's URI for the request locale.
 *                    allowedGrantTypes - List of the allowed grant types (org.forgerock.oauth2.core.GrantType) for the client.
 *                    allowedResponseTypes - List of the allowed response types for the client.
 *                    allowedScopes - List of the allowed scopes for the client.
 *                    customProperties - A map of the custom properties of the client.
 *                                       Lists or maps will be included as sub-maps; for example:
 *                                       customMap[Key1]=Value1 will be returned as customMap -> Key1 -> Value1.
 *                                       To add custom properties to a client, update the Custom Properties field
 *                                       in AM Console > Realm Name > Applications > OAuth 2.0 > Clients > Client ID > Advanced.
 * identity - AMIdentity (3).
 *            Always present, the identity of the resource owner.
 * session - SSOToken (4).
 *           Present if the request contains the session cookie, the user's session object.
 * scriptName - String (primitive).
 *              Always present, the display name of the script.
 * logger - Always present, the "OAuth2Provider" debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 *          Corresponding files will be prefixed with: scripts.OIDC_CLAIMS.
 * httpClient - HTTP Client (8).
 *              Always present, the HTTP Client instance:
 *              https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-http-client.html#scripting-api-global-http-client.
 *              In order to use the client, you may need to add
 *              org.forgerock.http.Client,
 *              org.forgerock.http.protocol.*,
 *              and org.forgerock.util.promise.PromiseImpl
 *              to the allowed Java classes in the scripting engine configuration, as described in:
 *              https://backstage.forgerock.com/docs/am/7/scripting-guide/script-engine-security.html
 *
 * Return - a new UserInfoClaims(Map<String, Object> values, Map<String, List<String>> compositeScopes) (1) object.
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *          See RESULTS section for additional details.
 *
 * Class reference:
 * (1) UserInfoClaims - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/oauth2/core/UserInfoClaims.html.
 * (2) Claim - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/openidconnect/Claim.html).
 *         An instance of org.forgerock.openidconnect.Claim has methods to access
 *         the claim name, requested values, locale, and whether the claim is essential.
 * (3) AMIdentity - https://backstage.forgerock.com/docs/am/7/apidocs/com/sun/identity/idm/AMIdentity.html.
 * (4) SSOToken - https://backstage.forgerock.com/docs/am/7/apidocs/com/iplanet/sso/SSOToken.html.
 * (5) Map - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashMap.html,
 *           or https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 * (6) Set - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashSet.html.
 * (7) List - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ArrayList.html.
 * (8) Client - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/http/Client.html.
*/

(function () {
    // SETUP

    /**
     * Claim processing utilities.
     * An object that contains reusable functions for processing claims.
     * @see CLAIM PROCESSING UTILITIES section for details.
     */
    var utils = getUtils();

    // CONFIGURATION AND CUSTOMIZATION

    /**
     * OAuth 2.0 scope values (scopes) can be used by the Client to request OIDC claims.
     *
     * Call this configuration method, and pass in as the first argument
     * an object that maps a scope value to an array of claim names
     * to specify which claims need to be processed and returned for the requested scopes.
     * @see {@link https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims}
     * for the scope values that could be used to request claims as defined in the OIDC specification.
     *
     * Below, find a default configuration that is expected to work in the current environment.
     *
     * CUSTOMIZATION
     * You can choose the claim names returned for a scope.
     */
    utils.setScopeClaimsMap({
        profile: [
            'name',
            'family_name',
            'given_name',
            'zoneinfo',
            'locale'
        ],
        email: ['email'],
        address: ['address'],
        phone: ['phone_number']
    });

    /**
     * In this script, each claim
     * derived from the requested scopes,
     * provided by the authorization server, and
     * requested by the client via the claims parameter
     * will be processed by a function associated with the claim name.
     *
     * Call this configuration method, and pass in as the first argument
     * an object that maps a claim name to a resolver function,
     * which will be automatically executed for each claim processed by the script.
     *
     * The claim resolver function will receive the requested claim information
     * in an instance of org.forgerock.openidconnect.Claim as the first argument.
     * @see {@link https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/openidconnect/Claim.html}
     * for details on the Claim class.
     *
     * If the claim resolver function returns a value,
     * other than undefined or null,
     * the claim will be included in the script's results.
     *
     * The Claim instance provides methods to check
     * what the name of the claim is,
     * which values the claim request contains,
     * whether the claim is essential, and
     * which locale the claim is associated with.
     * The resolver function can consider this information when computing and returning the claim value.
     *
     * Below, find a default configuration that is expected to work in the current environment.
     * A reusable function, utils.getUserProfileClaimResolver(String attribute-name),
     * is called to return a claim resolver function based on a user profile attribute.
     * @see CLAIM RESOLVERS section for the implementation details and examples.
     * For the address claim, an example of a claim resolver that uses another claim resolver is provided.
     *
     * CUSTOMIZATION
     * You can reuse the predefined utils methods with your custom arguments.
     * You can also specify a custom resolver function for a claim name,
     * that will compute and return the claim value—as shown in the commented out example below.
     */
    utils.setClaimResolvers({
        /*
        // An example of a simple claim resolver function that is defined for a claim
        // directly in the configuration object:
        custom-claim-name: function (requestedClaim) {
            // In this case, initially, the claim value comes straight from a user profile attribute value:
            var claimValue = identity.getAttribute('custom-attribute-name').toArray()[0]

            // Optionally, provide additional logic for processing (filtering, formatting, etc.) the claim value.
            // You can use:
            // requestedClaim.getName()
            // requestedClaim.getValues()
            // requestedClaim.getLocale()
            // requestedClaim.isEssential()

            return claimValue
        },
        */
        /**
         * The use of utils.getUserProfileClaimResolver shows how
         * an argument passed to a function that returns a claim resolver
         * becomes available to the resolver function (via its lexical context).
         */
        name: utils.getUserProfileClaimResolver('cn'),
        family_name: utils.getUserProfileClaimResolver('sn'),
        given_name: utils.getUserProfileClaimResolver('givenname'),
        zoneinfo: utils.getUserProfileClaimResolver('preferredtimezone'),
        locale: utils.getUserProfileClaimResolver('preferredlocale'),
        email: utils.getUserProfileClaimResolver('mail'),
        address: utils.getAddressClaimResolver(
            /**
             * The passed in user profile claim resolver function
             * can be used by the address claim resolver function
             * to obtain the claim value to be formatted as per the OIDC specification:
             * @see https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim.
             */
            utils.getUserProfileClaimResolver('postaladdress')
        ),
        phone_number: utils.getUserProfileClaimResolver('telephonenumber')
    });

    // CLAIM PROCESSING UTILITIES

    /**
     * @returns {object} An object that contains reusable claim processing utilities.
     * @see PUBLIC METHODS section and the return statement for the list of exported functions.
     */
    function getUtils () {
        // IMPORT JAVA

        /**
         * Provides Java scripting functionality.
         * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Scripting_Java#javaimporter_constructor}.
         */
        var frJava = JavaImporter(
            org.forgerock.oauth2.core.exceptions.InvalidRequestException,
            org.forgerock.oauth2.core.UserInfoClaims,
            org.forgerock.openidconnect.Claim,

            java.util.LinkedHashMap,
            java.util.ArrayList
        );

        // SET UP CONFIGURATION

        /**
         * Placeholder for a configuration option that contains
         * an object that maps the supported scope values (scopes)
         * and the corresponding claim names for each scope value.
         */
        var scopeClaimsMap;

        /**
         * Placeholder for a configuration option that contains
         * an object that maps the supported claim names
         * and the resolver functions returning the claim value.
         */
        var claimResolvers;

        /**
         * A (public) method that accepts an object that maps the supported scopes and the corresponding claim names,
         * and assigns it to a (private) variable that serves as a configuration option.
         * @param {object} params - An object that maps each supported scope value to an array of claim names,
         * in order to specify which claims need to be processed for the requested scopes.
         * @see {@link https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims} for details.
         * @param {string[]} [params.profile] - An array of claim names to be returned if the profile scope is requested.
         * @param {string[]} [params.email] - An array of claim names to be returned if the email scope is requested.
         * @param {string[]} [params.address] - An array of claim names to be returned if the address scope is requested.
         * @param {string[]} [params.phone] - An array of claim names to be returned if the phone scope is requested.
         * @returns {undefined}
         */
        function setScopeClaimsMap(params) {
            scopeClaimsMap = params;
        }

        /**
         * A (public) method that accepts an object that maps the supported claim names
         * and the resolver functions returning the claim value,
         * and assigns it to a (private) variable that serves as a configuration option.
         * @param {object} params - An object that maps
         * each supported claim name to a function that computes and returns the claim value.
         */
        function setClaimResolvers(params) {
            claimResolvers = params;
        }

        // CLAIM RESOLVERS

        /**
         * Claim resolvers are functions that return a claim value.
         * @param {*}
         * @returns {*}
         */

        /**
         * Defines a claim resolver based on a user profile attribute.
         * @param {string} attributeName - Name of the user profile attribute.
         * @returns {function} A function that will determine the claim value
         * based on the user profile attribute and the (requested) claim properties.
         */
        function getUserProfileClaimResolver (attributeName) {
            /**
             * Resolves a claim with a user profile attribute value.
             * Returns undefined if the identity attribute is not populated,
             * OR if the claim has requested values that do not contain the identity attribute value.
             * ATTENTION: the aforementioned comparison is case-sensitive.
             * @param {org.forgerock.openidconnect.Claim} claim
             * An object that provides methods to obtain information/requirements associated with a claim.
             * @see {@link https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/openidconnect/Claim.html} for details.
             * @returns {string|HashSet|undefined}
             */
            function resolveClaim(claim) {
                var userProfileValue;

                if (identity) {
                    userProfileValue = getClaimValueFromSet(claim, identity.getAttribute(attributeName));

                    if (userProfileValue && !userProfileValue.isEmpty()) {
                        if (!claim.getValues() || claim.getValues().isEmpty() || claim.getValues().contains(userProfileValue)) {
                            return userProfileValue;
                        }
                    }
                }
            }

            return resolveClaim;
        }

        /**
         * Returns an address claim resolver based on a claim value obtained with another claim resolver.
         * @param {function} resolveClaim - A function that returns a claim value.
         * @returns {function} A function that will accept a claim as an argument,
         * run the claim resolver function for the claim and obtain the claim value,
         * and apply additional formatting to the value before returning it.
         */
        function getAddressClaimResolver (resolveClaim) {
            /**
             * Creates an address claim object from a value returned by a claim resolver,
             * and returns the address claim object as the claim value.
             * @see {@link https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim}.
             * The claim value is obtained with a claim resolving function available from the closure.
             * @param {org.forgerock.openidconnect.Claim} claim
             * An object that provides methods to obtain information/requirements associated with a claim.
             * @see {@link https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/openidconnect/Claim.html} for details.
             * @returns {java.util.LinkedHashMap|undefined} The address claim object created from a claim value.
             */
            function resolveAddressClaim(claim) {
                var claimValue = resolveClaim(claim);
                var addressObject;

                if (isClaimValueValid(claimValue)) {
                    addressObject = new frJava.LinkedHashMap();

                    addressObject.put('formatted', claimValue);

                    return addressObject;
                }
            }

            return resolveAddressClaim;
        }

        /**
         * Returns an essential claim resolver based on a claim value obtained with another claim resolver.
         * @param {function} resolveClaim - A function that returns a claim value.
         * @returns {function} A function that will accept a claim as an argument,
         * run the claim resolver function for the claim and obtain the claim value,
         * and apply additional logic for essential claims.
         */
        function getEssentialClaimResolver (resolveClaim) {
            /**
             * Returns a claim value or throws an error.
             * The claim value is obtained with a claim resolving function available from the closure.
             * Throws an exception if the claim is essential and no value is returned for the claim.
             *
             * Use of this resolver is optional.
             * @see {@link https://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests} stating:
             * "Note that even if the Claims are not available because the End-User did not authorize their release or they are not present,
             * the Authorization Server MUST NOT generate an error when Claims are not returned, whether they are Essential or Voluntary,
             * unless otherwise specified in the description of the specific claim."
             *
             * @param {org.forgerock.openidconnect.Claim} claim
             * An object that provides methods to obtain information/requirements associated with a claim.
             * @see {@link https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/openidconnect/Claim.html} for details.
             * @returns {*}
             * @throws {org.forgerock.oauth2.core.exceptions.InvalidRequestException}
             */
            function resolveEssentialClaim(claim) {
                var claimValue = resolveClaim(claim);

                if (claim.isEssential() && !isClaimValueValid(claimValue)) {
                    throw new frJava.InvalidRequestException('Could not provide value for essential claim: ' + claim.getName());
                }

                return claimValue;
            }

            return resolveEssentialClaim;
        }

        /**
         * Provides default resolution for a claim.
         * Use it if a claim-specific resolver is not defined in the configuration.
         * @param {org.forgerock.openidconnect.Claim} claim
         * An object that provides methods to obtain information/requirements associated with a claim.
         * @see {@link https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/openidconnect/Claim.html} for details.
         * @returns {*} A single value associated with this claim.
         */
        function resolveAnyClaim (claim) {
            if (claim.getValues().size() === 1) {
                return claim.getValues().toArray()[0];
            }
        }

        // UTILITIES

        /**
         * Returns claim value from a set.
         * If the set contains a single value, returns the value.
         * If the set contains multiple values, returns the set.
         * Otherwise, returns undefined.
         *
         * @param {org.forgerock.openidconnect.Claim} claim
         * An object that provides methods to obtain information/requirements associated with a claim.
         * @see {@link https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/openidconnect/Claim.html} for details.
         * @param {java.util.HashSet} set The set—for example, a user profile attribute value.
         * @returns {string|java.util.HashSet|undefined}
         */
        function getClaimValueFromSet (claim, set) {
            if (set && set.size()) {
                if (set.size() === 1) {
                    return set.toArray()[0];
                } else {
                    return set;
                }
            } else if (logger.warningEnabled()) {
                logger.warning('OIDC Claims script. Got an empty set for claim: ' + claim.getName());
            }
        }

        function isClaimValueValid (claimValue) {
            if (typeof claimValue === 'undefined' || claimValue === null) {
                return false;
            }

            return true;
        }

        // CLAIM PROCESSING

        /**
         * Constructs and returns an object populated with the computed claim values
         * and the requested scopes mapped to the claim names.
         * @returns {org.forgerock.oauth2.core.UserInfoClaims} The object to be returned to the authorization server.
         * @see {@link https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/oauth2/core/UserInfoClaims.html}.
         * @see RESULTS section for the use of this function.
         */
        function getUserInfoClaims () {
            return new frJava.UserInfoClaims(getComputedClaims(), getCompositeScopes());
        }

        /**
         * Creates a map of (requested) claim names populated with the computed claim values.
         * @returns {java.util.LinkedHashMap}
         * A map of the requested claim names and the corresponding claim values.
         */
        function getComputedClaims () {
            /**
             * Creates a complete list of claim objects from:
             * the claims derived from the scopes,
             * the claims provided by the authorization server,
             * and the claims requested by the client.
             * @returns {java.util.ArrayList}
             * Returns a complete list of org.forgerock.openidconnect.Claim objects available to the script.
             * @see {@link https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/openidconnect/Claim.html} for the claim object details.
             */
            function getClaims() {
                /**
                 * Returns a list of claim objects for the requested scopes.
                 * Uses the scopeClaimsMap configuration option to derive the claim names;
                 * no other properties of a claim derived from a scope are populated.
                 * @returns {java.util.ArrayList}
                 * A list of org.forgerock.openidconnect.Claim objects derived from the requested scopes.
                 * @see {@link https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/openidconnect/Claim.html} for the claim object details.
                 */
                function convertScopeToClaims() {
                    var claims = new frJava.ArrayList();

                    scopes.toArray().forEach(function (scope) {
                        if (String(scope) !== 'openid' && scopeClaimsMap[scope]) {
                            scopeClaimsMap[scope].forEach(function (claimName) {
                                claims.add(new frJava.Claim(claimName));
                            });
                        }
                    });

                    return claims;
                }

                var claims = new frJava.ArrayList();

                claims.addAll(convertScopeToClaims());
                claims.addAll(claimObjects);
                claims.addAll(requestedTypedClaims);

                return claims;
            }

            /**
             * Computes and returns a claim value.
             * To obtain the claim value, uses the resolver function specified for the claim in the claimResolvers configuration object.
             * @see claimResolvers
             * If no resolver function is found, uses the default claim resolver function.
             *
             * @param {org.forgerock.openidconnect.Claim} claim
             * An object that provides methods to obtain information/requirements associated with a claim.
             * @see {@link https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/openidconnect/Claim.html} for details.
             * @returns {*} Claim value.
             * @throws {org.forgerock.oauth2.core.exceptions.InvalidRequestException}
             * Rethrows this exception if a claim resolver throws it.
             * You can throw org.forgerock.oauth2.core.exceptions.InvalidRequestException from your custom claim resolver
             * if you want to terminate the claim processing.
             */
            function computeClaim(claim) {
                var resolveClaim;
                var message;

                try {
                    resolveClaim = claimResolvers[claim.getName()] || resolveAnyClaim;

                    return resolveClaim(claim);
                } catch (e) {
                    message = 'OIDC Claims script exception. Unable to resolve OIDC Claim. ' + e;

                    if (String(e).indexOf('org.forgerock.oauth2.core.exceptions.InvalidRequestException') !== -1) {
                        throw e;
                    }

                    if (logger.warningEnabled()) {
                        logger.warning(message);
                    }
                }
            }

            var computedClaims = new frJava.LinkedHashMap();

            getClaims().toArray().forEach(function (claim) {
                var claimValue = computeClaim(claim);

                if (isClaimValueValid(claimValue)) {
                    computedClaims.put(claim.getName(), claimValue);
                } else {
                    /**
                     * If a claim has been processed, but appears in the list again,
                     * and its value cannot be computed under the new conditions,
                     * the claim is removed from the final result.
                     *
                     * For example, a claim could be mapped to a scope and found in the user profile,
                     * but also requested by the client with required values that don't match the computed one.
                     * @see {link https://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests}.
                     * for the relevant OIDC specification details.
                     */
                    computedClaims.remove(claim.getName());
                }
            });

            return computedClaims;
        }

        /**
         * Creates a map of requested scopes and the corresponding claim names.
         * @returns {java.util.LinkedHashMap}
         */
        function getCompositeScopes () {
            var compositeScopes = new frJava.LinkedHashMap();

            scopes.toArray().forEach(function (scope) {
                var scopeClaims = new frJava.ArrayList();

                if (scopeClaimsMap[scope]) {
                    scopeClaimsMap[scope].forEach(function (claimName) {
                        scopeClaims.add(claimName);
                    });
                }

                if (scopeClaims.size()) {
                    compositeScopes.put(scope, scopeClaims);
                }
            });

            return compositeScopes;
        }

        // PUBLIC METHODS

        return {
            setScopeClaimsMap: setScopeClaimsMap,
            setClaimResolvers: setClaimResolvers,
            getUserProfileClaimResolver: getUserProfileClaimResolver,
            getAddressClaimResolver: getAddressClaimResolver,
            getEssentialClaimResolver: getEssentialClaimResolver,
            getUserInfoClaims: getUserInfoClaims
        };
    }

    // RESULTS

    /**
     * This script returns an instance of the org.forgerock.oauth2.core.UserInfoClaims class
     * populated with the computed claim values and
     * the requested scopes mapped to the claim names.
     * @see {@link https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/oauth2/core/UserInfoClaims.html}.
     *
     * Assigning it to a variable gives you an opportunity
     * to log the content of the returned value during development.
     */
    var userInfoClaims = utils.getUserInfoClaims();

    /*
    logger.error(scriptName + ' results:')
    logger.error('Values: ' + userInfoClaims.getValues())
    logger.error('Scopes: ' + userInfoClaims.getCompositeScopes())
    */

    return userInfoClaims;
}());
javascript

Open oidc-claims-extension.js in your browser.

policy-condition.js

View script
/*
 * Copyright 2015-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */
/**
 * This is a Policy Condition example script. It demonstrates how to access a user's information,
 * use that information in external HTTP calls and make a policy decision based on the outcome.
 */

var userAddress, userIP, resourceHost;

if (validateAndInitializeParameters()) {

    var countryFromUserAddress = getCountryFromUserAddress();
    logger.message("Country retrieved from user's address: " + countryFromUserAddress);
    var countryFromUserIP = getCountryFromUserIP();
    logger.message("Country retrieved from user's IP: " + countryFromUserIP);
    var countryFromResourceURI = getCountryFromResourceURI();
    logger.message("Country retrieved from resource URI: " + countryFromResourceURI);

    if (countryFromUserAddress === countryFromUserIP && countryFromUserAddress === countryFromResourceURI) {
        logger.message("Authorization Succeeded");
        responseAttributes.put("countryOfOrigin", [countryFromUserAddress]);
        authorized = true;
    } else {
        logger.message("Authorization Failed");
        authorized = false;
    }

} else {
    logger.message("Required parameters not found. Authorization Failed.");
    authorized = false;
}

/**
 * Use the user's address to lookup their country of residence.
 *
 * @returns {*} The user's country of residence.
 */
function getCountryFromUserAddress() {

    var request = new org.forgerock.http.protocol.Request();
    request.setUri("http://maps.googleapis.com/maps/api/geocode/json?address=" + encodeURIComponent(userAddress));
  	request.setMethod("GET");

    var response = httpClient.send(request).get();
    logResponse(response);

    var geocode = JSON.parse(response.getEntity().getString());
    var i;
    for (i = 0; i < geocode.results.length; i++) {
        var result = geocode.results[i];
        var j;
        for (j = 0; j < result.address_components.length; i++) {
            if (result.address_components[i].types[0] == "country") {
                return result.address_components[i].long_name;
            }
        }
    }
}

/**
 * Use the user's IP to lookup the country from which the request originated.
 *
 * @returns {*} The country from which the request originated.
 */
function getCountryFromUserIP() {
    var request = new org.forgerock.http.protocol.Request();
    request.setUri("http://ip-api.com/json/" + userIP);
  	request.setMethod("GET");

    var response = httpClient.send(request).get();
    logResponse(response);

    var result = JSON.parse(response.getEntity().getString());
    if (result) {
        return result.country;
    }
}

/**
 * Use the requested resource's host name to lookup the country where the resource is hosted.
 *
 * @returns {*} The country in which the resource is hosted.
 */
function getCountryFromResourceURI() {
    var request = new org.forgerock.http.protocol.Request();
    request.setUri("http://ip-api.com/json/" + encodeURIComponent(resourceHost));
  	request.setMethod("GET");

    var response = httpClient.send(request).get();
    logResponse(response);

    var result = JSON.parse(response.getEntity().getString());
    if (result) {
        return result.country;
    }
}

/**
 * Retrieve and validate the variables required to make the external HTTP calls.
 *
 * @returns {boolean} Will be true if validation was successful.
 */
function validateAndInitializeParameters() {
    var userAddressSet = identity.getAttribute("postalAddress");
    if (userAddressSet == null || userAddressSet.isEmpty()) {
        logger.warning("No address specified for user: " + username);
        return false;
    }
    userAddress = userAddressSet.iterator().next();
    logger.message("User address: " + userAddress);

    if (!environment) {
        logger.warning("No environment parameters specified in the evaluation request.");
        return false;
    }

    var ipSet = environment.get("IP");
    if (ipSet == null || ipSet.isEmpty()) {
        logger.warning("No IP specified in the evaluation request environment parameters.");
        return false;
    }
    userIP = ipSet.iterator().next();
    logger.message("User IP: " + userIP);

    if (!resourceURI) {
        logger.warning("No resource URI specified.");
        return false;
    }
    resourceHost = resourceURI.match(/^(.*:\/\/)(www\.)?([A-Za-z0-9\-\.]+)(:[0-9]+)?(.*)$/)[3];
    logger.message("Resource host: " + resourceHost);

    return true;
}

function logResponse(response) {
    logger.message("User REST Call. Status: " + response.getStatus() + ", Body: " + response.getEntity().getString());
}
javascript

Open policy-condition.js in your browser.

salesforce-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('user_id'));
    normalizedProfileData.put('displayName', rawProfile.get('name'));
    normalizedProfileData.put('givenName', rawProfile.get('given_name'));
    normalizedProfileData.put('familyName', rawProfile.get('family_name'));
    normalizedProfileData.put('photoUrl', rawProfile.get('picture'));
    normalizedProfileData.put('email', rawProfile.get('email'));
    normalizedProfileData.put('username', rawProfile.get('email'));
    normalizedProfileData.put('locale', rawProfile.get('zoneInfo'));

    return normalizedProfileData;
}());
javascript

Open salesforce-profile-normalization.js in your browser.

saml2-idp-adapter.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * The script has these top level functions that could be executed during a SAML2 flow.
 *      - preSingleSignOn
 *      - preAuthentication
 *      - preSendResponse
 *      - preSignResponse
 *      - preSendFailureResponse
 *
 * Please see the javadoc for the interface definition and more information about these methods.
 * https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/plugins/SAML2IdentityProviderAdapter.html
 * Note that the initialize method is not supported in the scripts.
 *
 * Defined variables. Check the documentation on the respective functions for the variables available to it.
 *
 * hostedEntityId - String
 *     Entity ID for the hosted IDP
 * realm - String
 *     Realm of the hosted IDP
 * idpAdapterScriptHelper - IdpAdapterScriptHelper (1)
 *     An instance of IdpAdapterScriptHelper containing helper methods. See Javadoc for more details.
 * request - HttpServletRequest (2)
 *     Servlet request object
 * response - HttpServletResponse (3)
 *     Servlet response object
 * authnRequest - AuthnRequest (4)
 *     The original authentication request sent from SP
 * reqId - String
 *     The id to use for continuation of processing if the adapter redirects
 * res - Response (5)
 *     The SAML Response
 * session - SSOToken (6)
 *     The single sign-on session. The reference type of this is Object and would need to be casted to SSOToken.
 * relayState - String
 *     The relayState that will be used in the redirect
 * faultCode - String
 *     the fault code that will be returned in the SAML response
 * faultDetail - String
 *     the fault detail that will be returned in the SAML response
 * logger - Logger instance
 *     https://backstage.forgerock.com/docs/am/7.3/scripting-guide/scripting-api-global-logger.html.
 *     Corresponding log files will be prefixed with: scripts.<script name>
 *
 * Throws SAML2Exception (7):
 *     for any exceptions occurring in the adapter. The federation process will continue
 *
 * Class reference:
 * (1) idpAdapterScriptHelper - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/plugins/scripted/IdpAdapterScriptHelper.html.
 * (2) HttpServletRequest - https://tomcat.apache.org/tomcat-10.1-doc/servletapi/jakarta/servlet/http//HttpServletRequest.html.
 * (3) HttpServletResponse - https://tomcat.apache.org/tomcat-10.1-doc/servletapi/jakarta/servlet/http//HttpServletResponse.html.
 * (4) AuthnRequest - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/protocol/AuthnRequest.html.
 * (5) Response - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/protocol/Response.html.
 * (6) SSOToken - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/iplanet/sso/SSOToken.html.
 * (7) SAML2Exception - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/common/SAML2Exception.html.
 */

/*
 * Template/default script for SAML2 IDP Adapter scripted plugin.
 */

/*
 * Available variables for preSingleSignOn:
 *     hostedEntityId
 *     realm
 *     idpAdapterScriptHelper
 *     request
 *     authnRequest
 *     response
 *     reqId
 *     logger
 *
 * Return - true if browser redirection is happening after processing, false otherwise. Default to false.
 */
function preSingleSignOn () {
    return false;
}

/*
 * Available variables for preAuthentication:
 *     hostedEntityId
 *     realm
 *     idpAdapterScriptHelper
 *     request
 *     authnRequest
 *     response
 *     reqId
 *     session
 *     relayState
 *     logger
 *
 * Return - true if browser redirection is happening after processing, false otherwise. Default to false.
 */
function preAuthentication () {
    return false;
}

/*
 * Available variables for preSendResponse:
 *     hostedEntityId
 *     realm
 *     idpAdapterScriptHelper
 *     request
 *     authnRequest
 *     response
 *     reqId
 *     session
 *     relayState
 *     logger
 *
 * Return - true if browser redirection happened after processing, false otherwise. Default to false.
 */
function preSendResponse () {
    return false;
}

/*
 * Available variables for preSignResponse:
 *     hostedEntityId
 *     realm
 *     idpAdapterScriptHelper
 *     request
 *     authnRequest
 *     session
 *     relayState
 *     res
 *     logger
 */
function preSignResponse () {
}

/*
 * Available variables for preSendFailureResponse:
 *     hostedEntityId
 *     realm
 *     idpAdapterScriptHelper
 *     request
 *     response
 *     faultCode
 *     faultDetail
 *     logger
 */
function preSendFailureResponse () {
}
javascript

Open saml2-idp-adapter.js in your browser.

saml2-idp-attribute-mapper.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns a list of SAML Attribute objects for the IDP framework to insert into the generated Assertion.
 *
 * Defined variables:
 * session - SSOToken (1)
 *           The single sign-on session.
 * hostedEntityId - String (primitive).
 *                  The hosted entity ID.
 * remoteEntityId - String (primitive).
 *                  The remote entity ID.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * logger - Always present, the debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 *          Corresponding log files will be prefixed with: scripts.SAML2_IDP_ATTRIBUTE_MAPPER
 * idpAttributeMapperScriptHelper - IdpAttributeMapperScriptHelper (2)
 *                                - An IdpAttributeMapperScriptHelper instance containing methods used for IDP attribute mapping.
 *
 * Throws SAML2Exception:
 *      - on failing to map the IDP attributes.
 *
 * Return - a list of SAML Attribute (3) objects.
 *
 * Class reference:
 * (1) SSOToken - https://backstage.forgerock.com/docs/am/7/apidocs/com/iplanet/sso/SSOToken.html.
 * (2) IdpAttributeMapperScriptHelper - https://backstage.forgerock.com/docs/am/7.2/apidocs/com/sun/identity/saml2/plugins/scripted/IdpAttributeMapperScriptHelper.html.
 * (3) Attribute - https://backstage.forgerock.com/docs/am/7/apidocs/com/sun/identity/saml2/assertion/Attribute.html.
 */

/**
 * Default SAML2 IDP Attribute Mapper.
 */
function getAttributes() {
    var frJava = JavaImporter(
        com.sun.identity.saml2.common.SAML2Exception
    );

    const debugMethod = "ScriptedIDPAttributeMapper.getAttributes:: ";

    try {

        if (!idpAttributeMapperScriptHelper.isSessionValid(session)) {
            logger.error(debugMethod + "Invalid session.");
            return null;
        }

        var configMap = idpAttributeMapperScriptHelper.getRemoteSPConfigAttributeMap(realm, remoteEntityId);
        logger.message(debugMethod + "Remote SP attribute map = {}", configMap);
        if (configMap == null || configMap.isEmpty()) {
            configMap = idpAttributeMapperScriptHelper.getHostedIDPConfigAttributeMap(realm, hostedEntityId);
            if (configMap == null || configMap.isEmpty()) {
                logger.message(debugMethod + "Configuration map is not defined.");
                return null;
            }
            logger.message(debugMethod + "Hosted IDP attribute map = {}", configMap);
        }

        var attributes = new java.util.ArrayList();
        var stringValueMap = new java.util.HashSet();
        var binaryValueMap;
        var localAttribute;

        // Don't try to read the attributes from the datastore if the ignored profile is enabled in this realm.
        if (!idpAttributeMapperScriptHelper.isIgnoredProfile(session, realm)) {
            try {
                // Resolve attributes to be read from the datastore.
                var stringAttributes = new java.util.HashSet();
                var binaryAttributes = new java.util.HashSet();
                var keyIter = configMap.keySet().iterator();
                while (keyIter.hasNext()) {
                    var key = keyIter.next();
                    localAttribute = configMap.get(key);
                    if (!idpAttributeMapperScriptHelper.isStaticAttribute(localAttribute)) {
                        if (idpAttributeMapperScriptHelper.isBinaryAttribute(localAttribute)) {
                            // add it to the list of attributes to treat as being binary
                            binaryAttributes.add(idpAttributeMapperScriptHelper.removeBinaryAttributeFlag(localAttribute));
                        } else {
                            stringAttributes.add(localAttribute);
                        }
                    }
                }

                if (!stringAttributes.isEmpty()) {
                    stringValueMap = idpAttributeMapperScriptHelper.getAttributes(session, stringAttributes);
                }
                if (!binaryAttributes.isEmpty()) {
                    binaryValueMap = idpAttributeMapperScriptHelper.getBinaryAttributes(session, binaryAttributes);
                }
            } catch (error) {
                logger.error(debugMethod + "Error accessing the datastore. " + error);
                //continue to check in ssotoken.
            }
        }

        var keyIter = configMap.keySet().iterator();
        while (keyIter.hasNext()) {
            var key = keyIter.next()
            var nameFormat = null;
            var samlAttribute = key;
            localAttribute = configMap.get(key);
            // check if samlAttribute has format nameFormat|samlAttribute
            var samlAttributes = String(new java.lang.String(samlAttribute));
            var tokens = samlAttributes.split('|');

            if (tokens.length > 1) {
                nameFormat = tokens[0];
                samlAttribute = tokens[1];
            }

            var attributeValues = new java.util.HashSet();
            if (idpAttributeMapperScriptHelper.isStaticAttribute(localAttribute)) {
                // Remove the static flag before using it as the static value
                localAttribute = idpAttributeMapperScriptHelper.removeStaticAttributeFlag(localAttribute);
                attributeValues = new java.util.HashSet([localAttribute]);
                logger.message(debugMethod + "Adding static value {} for attribute named {}", localAttribute, samlAttribute);
            } else {
                if (idpAttributeMapperScriptHelper.isBinaryAttribute(localAttribute)) {
                    // Remove the flag as not used for lookup
                    localAttribute = idpAttributeMapperScriptHelper.removeBinaryAttributeFlag(localAttribute);
                    attributeValues = idpAttributeMapperScriptHelper.getBinaryAttributeValues(samlAttribute, localAttribute,
                        binaryValueMap);
                } else {
                    if (stringValueMap != null && !stringValueMap.isEmpty()) {
                        attributeValues = stringValueMap.get(localAttribute);
                    } else {
                        logger.message(debugMethod + "{} string value map was empty or null.", localAttribute);
                    }
                }

                // If all else fails, try to get the value from the users ssoToken
                if (attributeValues == null || attributeValues.isEmpty()) {
                    logger.message(debugMethod + "User profile does not have value for {}, checking SSOToken.", localAttribute);
                    attributeValues = new java.util.HashSet(idpAttributeMapperScriptHelper.getPropertySet(session, localAttribute));
                }
            }

            if (attributeValues == null || attributeValues.isEmpty()) {
                logger.message(debugMethod + "{} not found in user profile or SSOToken.", localAttribute);
            } else {
                attributes.add(idpAttributeMapperScriptHelper.createSAMLAttribute(samlAttribute, nameFormat, attributeValues));
            }
        }

        return attributes;

    } catch (error) {
        logger.error(debugMethod + "Error mapping IDP attributes. " + error);
        throw new frJava.SAML2Exception(error);
    }
}

getAttributes();
javascript

Open saml2-idp-attribute-mapper.js in your browser.

saml2-nameid-mapper.js

View script
/*
 * Copyright 2024-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This is an example SAML2 NameID Mapper script.
 * This script should return a string value representing the SAML2 NameID identifier.
 * The example script delegates to the configured java plugin via the nameIDScriptHelper binding.
 */
nameIDScriptHelper.getNameIDValue();
javascript

Open saml2-nameid-mapper.js in your browser.

saml2-sp-adapter.js

View script
/*
 * Copyright 2023-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * The script has these top level functions that could be executed during a SAML2 flow.
 *      - preSingleSignOnRequest
 *      - preSingleSignOnProcess
 *      - postSingleSignOnSuccess
 *      - postSingleSignOnFailure
 *      - postNewNameIDSuccess
 *      - postTerminateNameIDSuccess
 *      - preSingleLogoutProcess
 *      - postSingleLogoutSuccess
 *
 * Please see the JavaDoc for the interface for more information about these methods.
 * https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/org/forgerock/openam/saml2/plugins/SPAdapter.html
 * Note that the initialize method is not supported in the scripts.
 *
 * Defined variables. Check the documentation on the respective functions for the variables available to it.
 *
 * hostedEntityId - String
 *     Entity ID for the hosted SP
 * realm - String
 *     Realm of the hosted SP
 * idpEntityId - String
 *     The entity ID for the Identity Provider for which the sign-on request will be sent.
 * request - HttpServletRequest (1)
 *     Servlet request object
 * response - HttpServletResponse (2)
 *     Servlet response object
 * authnRequest - AuthnRequest (3)
 *     The authentication request sent that is sent from the Service Provider.
 * session - SSOToken (4)
 *     The single sign-on session. The reference type of this is Object and would need to be casted to SSOToken.
 * res - Response (5)
 *     The SSO Response received from the Identity Provider.
 * profile - String
 *     The protocol profile that is used, this will be one of the following values from SAML2Constants (6):
 *          - SAML2Constants.HTTP_POST
 *          - SAML2Constants.HTTP_ARTIFACT
 *          - SAML2Constants.PAOS
 * out - PrintWriter (7)
 *     The PrintWriter that can be used to write to.
 * isFederation - boolean
 *     Set to true if using federation, otherwise false.
 * failureCode - int
 *     An integer holding the failure code when an error has occurred. For potential values see SPAdapter.
 * userId - String
 *     The unique universal ID of the user with whom the new name identifier request was performed.
 * idRequest - ManageNameIDRequest (8)
 *     The new name identifier request, this will be null if the request object is not available
 * idResponse - ManageNameIDResponse (9)
 *     The new name identifier response, this will be null if the response object is not available
 * binding - String
 *     The binding used for the new name identifier request. This will be one of the following values:
 *          - SAML2Constants.SOAP
 *          - SAML2Constants.HTTP_REDIRECT
 * logoutRequest - LogoutRequest (10)
 *     The single logout request.
 * logoutResponse - LogoutResponse (11)
 *     The single logout response.
 * spAdapterScriptHelper - SpAdapterScriptHelper (12)
 *     An instance of SpAdapterScriptHelper containing helper methods. See Javadoc for more details.
 * logger - Logger instance
 *     https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 *     Corresponding log files will be prefixed with: scripts.<script name>
 *
 * Throws SAML2Exception (13):
 *     for any exceptions occurring in the adapter. The federation process will continue
 *
 * Class reference:
 * (1) HttpServletRequest - https://tomcat.apache.org/tomcat-10.1-doc/servletapi/jakarta/servlet/http//HttpServletRequest.html.
 * (2) HttpServletResponse - https://tomcat.apache.org/tomcat-10.1-doc/servletapi/jakarta/servlet/http//HttpServletResponse.html.
 * (3) AuthnRequest - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/protocol/AuthnRequest.html.
 * (4) SSOToken - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/iplanet/sso/SSOToken.html.
 * (5) Response - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/protocol/Response.html
 * (6) SAML2Constants - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/common/SAML2Constants.html
 * (7) PrintWriter - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/PrintWriter.html
 * (8) ManageNameIDRequest - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/protocol/ManageNameIDRequest.html
 * (9) ManageNameIDResponse - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/protocol/ManageNameIDResponse.html
 * (10) LogoutRequest - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/protocol/LogoutRequest.html
 * (11) LogoutResponse - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/protocol/LogoutResponse.html
 * (12) SpAdapterScriptHelper - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/plugins/scripted/SpAdapterScriptHelper.html.
 * (13) SAML2Exception - https://backstage.forgerock.com/docs/am/7.3/_attachments/apidocs/com/sun/identity/saml2/common/SAML2Exception.html.
 */

/*
 * Template/default script for SAML2 SP Adapter scripted plugin.
 */

/*
 * Available variables for preSingleSignOnRequest:
 *     hostedEntityId
 *     idpEntityId
 *     realm
 *     request
 *     response
 *     authnRequest
 *     spAdapterScriptHelper
 *     logger
 */
function preSingleSignOnRequest() {
}

/*
 * Available variables for preSingleSignOnProcess:
 *     hostedEntityId
 *     realm
 *     request
 *     response
 *     authnRequest
 *     res
 *     profile
 *     spAdapterScriptHelper
 *     logger
 */
function preSingleSignOnProcess() {
}

/*
 * Available variables for postSingleSignOnSuccess:
 *     hostedEntityId
 *     realm
 *     request
 *     response
 *     out
 *     session
 *     authnRequest
 *     res
 *     profile
 *     isFederation
 *     spAdapterScriptHelper
 *     logger
 *
 * Return - true if response is being redirected, false if not. Default to false.
 */
function postSingleSignOnSuccess() {
    return false;
}

/*
 * Available variables for postSingleSignOnFailure:
 *     hostedEntityId
 *     realm
 *     request
 *     response
 *     authnRequest
 *     res
 *     profile
 *     failureCode
 *     spAdapterScriptHelper
 *     logger
 *
 * Return - true if response is being redirected, false if not. Default to false.
 */
function postSingleSignOnFailure() {
    return false;
}

/*
 * Available variables for postNewNameIDSuccess:
 *     hostedEntityId
 *     realm
 *     request
 *     response
 *     userId
 *     idRequest
 *     idResponse
 *     binding
 *     spAdapterScriptHelper
 *     logger
 */
function postNewNameIDSuccess() {
}

/*
 * Available variables for postTerminateNameIDSuccess:
 *     hostedEntityId
 *     realm
 *     request
 *     response
 *     userId
 *     idRequest
 *     idResponse
 *     binding
 *     spAdapterScriptHelper
 *     logger
 */
function postTerminateNameIDSuccess() {
}

/*
 * Available variables for preSingleLogoutProcess:
 *     hostedEntityId
 *     realm
 *     request
 *     response
 *     userId
 *     logoutRequest
 *     logoutResponse
 *     binding
 *     spAdapterScriptHelper
 *     logger
 */
function preSingleLogoutProcess() {
}

/*
 * Available variables for postSingleLogoutSuccess:
 *     hostedEntityId
 *     realm
 *     request
 *     response
 *     userId
 *     logoutRequest
 *     logoutResponse
 *     binding
 *     spAdapterScriptHelper
 *     logger
 */
function postSingleLogoutSuccess() {
}
javascript

Open saml2-sp-adapter.js^ in your browser.

social-idp-profile-transformation.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - JsonValue (1).
 *              The social identity provider profile information for the authenticating user.
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

/**
 * Default Social Identity Provider Profile Transformation script to use as a template for new scripts.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    /**
     * Add profile data.
     * @example
     * normalizedProfileData.put('id', rawProfile.get('sub'));
     */

    return normalizedProfileData;
}());
javascript

Open social-idp-profile-transformation.js in your browser.

twitter-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('id_str'));
    normalizedProfileData.put('displayName', rawProfile.get('name'));
    normalizedProfileData.put('photoUrl', rawProfile.get('profile_image_url'));
    normalizedProfileData.put('email', rawProfile.get('email'));
    normalizedProfileData.put('username', rawProfile.get('screen_name'));

    return normalizedProfileData;
}());
javascript

Open twitter-profile-normalization.js in your browser.

vkontakte-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('id'));
    normalizedProfileData.put('displayName', rawProfile.get('first_name'));
    normalizedProfileData.put('givenName', rawProfile.get('first_name'));
    normalizedProfileData.put('familyName', rawProfile.get('last_name'));
    normalizedProfileData.put('photoUrl', rawProfile.get('photo_50'));
    normalizedProfileData.put('email', rawProfile.get('email'));
    normalizedProfileData.put('username', rawProfile.get('email'));

    return normalizedProfileData;
}());
javascript

Open vkontakte-profile-normalization.js in your browser.

wechat-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('openid'));
    normalizedProfileData.put('displayName', rawProfile.get('nickname'));
    normalizedProfileData.put('photoUrl', rawProfile.get('headimgurl'));
    normalizedProfileData.put('username', rawProfile.get('nickname'));

    return normalizedProfileData;
}());
javascript

Open wechat-profile-normalization.js in your browser.

wordpress-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('username'));
    normalizedProfileData.put('displayName', rawProfile.get('display_name'));
    normalizedProfileData.put('photoUrl', rawProfile.get('avatar_URL'));
    normalizedProfileData.put('email', rawProfile.get('email'));
    normalizedProfileData.put('username', rawProfile.get('username'));

    return normalizedProfileData;
}());
javascript

Open wordpress-profile-normalization.js in your browser.

yahoo-profile-normalization.js

View script
/*
 * Copyright 2021-2025 Ping Identity Corporation. All Rights Reserved
 *
 * This code is to be used exclusively in connection with Ping Identity
 * Corporation software or services. Ping Identity Corporation only offers
 * such software or services to legal entities who have entered into a
 * binding license agreement with Ping Identity Corporation.
 */

/*
 * This script returns the social identity profile information for the authenticating user
 * in a standard form expected by the Social Provider Handler Node.
 *
 * Defined variables:
 * rawProfile - The social identity provider profile information for the authenticating user.
 *              JsonValue (1).
 * logger - The debug logger instance:
 *          https://backstage.forgerock.com/docs/am/7/scripting-guide/scripting-api-global-logger.html#scripting-api-global-logger.
 * realm - String (primitive).
 *         The name of the realm the user is authenticating to.
 * requestHeaders - TreeMap (2).
 *                  The object that provides methods for accessing headers in the login request:
 *                  https://backstage.forgerock.com/docs/am/7/authentication-guide/scripting-api-node.html#scripting-api-node-requestHeaders.
 * requestParameters - TreeMap (2).
 *                     The object that contains the authentication request parameters.
 * selectedIdp - String (primitive).
 *               The social identity provider name. For example: google.
 * sharedState - LinkedHashMap (3).
 *               The object that holds the state of the authentication tree and allows data exchange between the stateless nodes:
 *               https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 * transientState - LinkedHashMap (3).
 *                  The object for storing sensitive information that must not leave the server unencrypted,
 *                  and that may not need to persist between authentication requests during the authentication session:
 *                  https://backstage.forgerock.com/docs/am/7/auth-nodes/core-action.html#accessing-tree-state.
 *
 * Return - a JsonValue (1).
 *          The result of the last statement in the script is returned to the server.
 *          Currently, the Immediately Invoked Function Expression (also known as Self-Executing Anonymous Function)
 *          is the last (and only) statement in this script, and its return value will become the script result.
 *          Do not use "return variable" statement outside of a function definition.
 *
 *          This script's last statement should result in a JsonValue (1) with the following keys:
 *          {
 *              {"displayName": "corresponding-social-identity-provider-value"},
 *              {"email": "corresponding-social-identity-provider-value"},
 *              {"familyName": "corresponding-social-identity-provider-value"},
 *              {"givenName": "corresponding-social-identity-provider-value"},
 *              {"id": "corresponding-social-identity-provider-value"},
 *              {"locale": "corresponding-social-identity-provider-value"},
 *              {"photoUrl": "corresponding-social-identity-provider-value"},
 *              {"username": "corresponding-social-identity-provider-value"}
 *          }
 *
 *          The consumer of this data defines which keys are required and which are optional.
 *          For example, the script associated with the Social Provider Handler Node and,
 *          ultimately, the managed object created/updated with this data
 *          will expect certain keys to be populated.
 *          In some common default configurations, the following keys are required:
 *          username, givenName, familyName, email.
 *
 * (1) JsonValue - https://backstage.forgerock.com/docs/am/7/apidocs/org/forgerock/json/JsonValue.html.
 * (2) TreeMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/TreeMap.html.
 * (3) LinkedHashMap - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedHashMap.html.
 */

(function () {
    var frJava = JavaImporter(
        org.forgerock.json.JsonValue
    );

    var normalizedProfileData = frJava.JsonValue.json(frJava.JsonValue.object());

    normalizedProfileData.put('id', rawProfile.get('sub'));
    normalizedProfileData.put('displayName', rawProfile.get('name'));
    normalizedProfileData.put('givenName', rawProfile.get('given_name'));
    normalizedProfileData.put('familyName', rawProfile.get('family_name'));
    normalizedProfileData.put('photoUrl', rawProfile.get('picture'));
    normalizedProfileData.put('email', rawProfile.get('email'));
    normalizedProfileData.put('username', rawProfile.get('email'));
    normalizedProfileData.put('locale', rawProfile.get('locale'));

    return normalizedProfileData;
}());
javascript

Open yahoo-profile-normalization.js in your browser.