Use case: Create dynamically branded journeys in ForgeRock Identity Cloud

Use case overview

Providing a tailored brand experience for users during authentication and self-service is a common use case that is easily implemented in ForgeRock Identity Cloud. This involves applying brand-specific themes that include visual elements such as logos, colors and layout.

Users may have a relationship with multiple brands in your company and may have the ability to select the brand they wish to interact with. Alternatively, the relevant brand may be dynamically applied based on specific contextual factors.

There are two approaches you can take to create a brand-specific user journey in Identity Cloud. The first involves multiple user journeys, each tailored to a specific brand. The second approach allows for dynamically switching a journey during execution, based on user preferences or other factors. Using this single-journey approach streamlines the journey administration.

The Organization model in Identity Cloud allows you to establish a relationship between a brand and a user. This relationship can then be used during journey execution to select a brand and modify the user experience on the fly.

In this use case, we’ll demonstrate how to:

Define brand-based themes using Hosted Pages

Themes are configured in Identity Cloud’s Hosted Pages. A theme defines the look and feel of the end-user and login UIs.

For this use case, we’ll create two different themes that represent the look and feel of two different brands. These themes will be dynamically applied based on an end user’s relationship with the brand.

To create a theme:

  1. Sign in to the Identity Cloud admin UI using your admin tenant URL, in the format https://<tenant-name>/am/XUI/?realm=/#/.

  2. Go to Hosted Pages > New Theme.

  3. Create your theme, following the guidance provided in Customize Identity Cloud end-user and login UI themes.

In our example, we’ve created two themes, called Brand1 and Brand2.

The layout, logos and styles have been customized according to specific brand requirements. These changes can be made for both the login Journey Pages and the end user’s Account Pages.


Customized Journey Pages for Brand1


Customized Account Pages for Brand1


Customized Journey Pages for Brand2


Customized Account Pages for Brand2

Configure the Organization model with brand information

We’ll use the Organization model to correlate users with specific brands.

The first step is to extend the organization schema with a new property to identify a theme that will be used with each organization:

  1. Sign in to the Identity Cloud admin UI using your admin tenant URL, in the format https://<tenant-name>/am/XUI/?realm=/#/.

  2. Go to Native Consoles > Identity Management > Configure > Managed Objects.

  3. Click Alpha_organization.

  4. Click Add a Property and create a new string type property called theme to hold the theme information.

  5. Click Save.

This new theme property will be available on any instance of alpha_organization.

Next, we’ll create two new alpha_organizations, called Company-A and Company-B, and apply the Brand1 and Brand2 themes, respectively:

  1. In the Identity Cloud admin UI, go to Identities > Manage > Alpha realm - Organizations.

  2. Click New Alpha realm - Organization.

  3. Enter a name for the organization (Company-A) and click Save.

  4. Enter Brand1 in the Brand field.

  5. Click Save.

  6. Repeat steps 1 to 5 to create Company-B with Brand2.

We’ll now add Members to the organizations. This will relate existing users with the organization, and correspondingly, with the brand assigned to that organization.

  1. In the Identity Cloud admin UI, go to Identities > Manage > Alpha realm - Organizations.

  2. Click on the Organization you want to add members to (in this case, Company-A).

  3. Click Members > Add Members, add the usernames of the users you want to add, and click Save.

  4. Repeat steps 1 to 3 to add members to Company-B.

NOTE: You can assign a user to multiple organizations, which would require some logic to select the appropriate brand. In this example, it is assumed that a user is only associated with one organization.

Create a brand-switching journey

This journey brings together the Hosted Pages themes and organization data and dynamically switches brands based on the user.

There are several different ways that themes are used by a journey, for example:

  1. If the theme is not defined, a journey will use the default theme.

  2. The journey itself can override the default theme with a specified theme.

  3. Within the journey, the theme can be switched by the execution of a specific callback.

This use case covers option 3.

Create a script to collect and set the theme

First, create a script that will be used in a scripted node to collect and set the theme, similar to this example:

var nodeConfig = {
    nodeName: "SelectTheme",
    tenantFqdnEsv: "esv.tenant.env.fqdn",
    accessTokenStateField: "idmAccessToken",
    attributeToTest: "fr-idm-managed-organization-member",
    realm: "alpha",
    orgThemeProperty: "theme"
};
var nodeLogger = {
    debug: function(message) {
        logger.message("***" + nodeConfig.nodeName + " " + message);
    },
    warning: function(message) {
        logger.warning("***" + nodeConfig.nodeName + " " + message);
    },
    error: function(message) {
        logger.error("***" + nodeConfig.nodeName + " " + message);
    },
};
var nodeOutcomes = {
    SUCCESS: "success",
    ERROR: "error",
};

var javaImports = JavaImporter(
    org.forgerock.openam.auth.node.api.Action,
    org.forgerock.openam.authentication.callbacks.PollingWaitCallback
);

var tenantFqdn;
var idmToken = null;
var orgId = null;
var themeId = "Starter Theme";
var username;

function getAccessToken() {
    return nodeState.get(nodeConfig.accessTokenStateField).asString();
}

function getUserOrg() {
    nodeLogger.debug("Getting User Org");
    var idmUserQueryURI = "https://".concat(tenantFqdn).concat("/openidm/managed/").concat(nodeConfig.realm).concat("_user?_queryFilter=_id%20eq%20%22").concat(username).concat("%22&_fields=userName,memberOfOrgIDs");
    var request = new org.forgerock.http.protocol.Request();
    request.setMethod('GET');
    request.setUri(idmUserQueryURI);
    request.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
    request.getHeaders().add("Authorization", "Bearer " + idmToken);
    var response = httpClient.send(request).get();
    var result = JSON.parse(response.getEntity().getString());
    orgId = result.result[0].memberOfOrgIDs[0];
    nodeLogger.debug("User Org: " + orgId);
}

function getOrgTheme() {
    nodeLogger.debug("Getting Org Theme");
    var idmUserOrgURI = "https://".concat(tenantFqdn).concat("/openidm/managed/").concat(nodeConfig.realm).concat("_organization?_queryFilter=_id%20eq%20%22").concat(orgId).concat("%22&_fields=name,").concat(nodeConfig.orgThemeProperty);
    var request = new org.forgerock.http.protocol.Request();
    request.setMethod('GET');
    request.setUri(idmUserOrgURI);
    request.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
    request.getHeaders().add("Authorization", "Bearer " + idmToken);
    var response = httpClient.send(request).get();
    var result = JSON.parse(response.getEntity().getString());
    if (result.result[0].theme) {
        themeId = result.result[0].theme;
    }
    nodeLogger.debug("Selected Theme: " + themeId);
}


(function() {
    try {
        nodeLogger.debug("Node starting");
        if (callbacks.isEmpty()) {
            tenantFqdn = systemEnv.getProperty(nodeConfig.tenantFqdnEsv);
            if (!tenantFqdn) {
                nodeLogger.error("Couldn't get FQDN from esv: ".concat(config.tenantFqdnEsv));
                action = javaImports.Action.goTo(nodeOutcomes.ERROR).build();
                return;
            }
            nodeLogger.debug("FQDN: " + tenantFqdn);

            username = nodeState.get("_id").asString();
            var userAttributeValue = idRepository.getAttribute(username, nodeConfig.attributeToTest);
            nodeLogger.debug("UserAttributeValue: " + userAttributeValue);

            if (userAttributeValue) {
                idmToken = getAccessToken();
                nodeLogger.debug("Access Token: " + idmToken);
                getUserOrg();
                if (orgId) {
                    getOrgTheme();
                }
            }

            var stage = JSON.stringify({
                themeId: themeId
            });
            action = javaImports.Action.send(
                new javaImports.PollingWaitCallback("100", "Please wait ...")
            ).withStage(stage).build();
        } else {
            nodeLogger.debug("Selected Theme: " + themeId);
            nodeState.putShared("selectedTheme", themeId);
            action = javaImports.Action.goTo(nodeOutcomes.SUCCESS).build()
        }
    } catch (e) {
        var msg = "Exception occurred. Here is the exception: ".concat(e.toString());
        nodeState.putShared("exception", msg);
        nodeLogger.error(msg);
        outcome = nodeOutcomes.ERROR;
    }
})();

Some specific areas of the script to highlight include:

  • The script assumes an access token has been acquired and is available in a tree state. For details, see: How do I acquire an access token for Identity Cloud API calls in a Scripted Decision node?

  • The script collects the organization information for the user using the memberOfOrgIDs property, which is a flattened RDVP that lists the organizations that a user is a member of.

  • The script then makes a call to the IDM REST endpoint for organizations, using the organization ID collected from the user. The query returns the theme property for the organization.

  • If it finds a theme associated with the organization, it sets the theme dynamically in the journey using a PollingWaitCallback and a stage identifier:

    
      var stage = JSON.stringify({themeId: themeId});
       action = fr.Action.send(
       new fr.PollingWaitCallback("100", "Please wait ...")
       ).withStage(stage).build();
    
    
  • If no theme was found, it falls back to a default theme.

Create the user journey

  1. Sign in to the Identity Cloud admin UI using your admin tenant URL, in the format https://<tenant-name>/am/XUI/?realm=/#/.

  2. Go to Journeys > New Journey.

  3. Enter a unique name for the journey, select which identities will authenticate using this journey, (optionally) enter a journey description, and click Save.

  4. Create a journey similar to this:

    Node descriptions:

    • Platform Username - Prompts the user to enter their username. See Platform Username node for further information.

    • Identify Existing User - Verifies a user exists based on an identifying attribute, such as an email address, then makes the value of a specified attribute available in the shared node state. See Identify Existing User node for further information.

    • Get Access Token - This Scripted Decision node allows you to acquire an access token for subsequent API calls using a service account. See How do I acquire an access token for Identity Cloud API calls in a Scripted Decision node? for further information.

    • Select Brand - This Scripted Decision node uses the script you created previously to perform an authenticated REST call to collect organization information and determines the brand selection.

    • Platform Password - Collects the user’s password. See Platform Password node for further information.

    • Data Store Decision - Verifies that the username and password values match those in the data store configured for the realm. See Data Store Decision node for further information.

  5. Make sure that the Select Brand (Scripted Decision) node includes the script you created previously, for example:

  6. Click Save to save the journey.

The journey follows this logical flow:

  • Initially the journey does not specify a particular theme, which means it starts with the default theme.
  • The journey then collects the user identifier.
  • With the user identifier, the journey determines if the user is associated with an organization.
  • If the user is associated with an organization, the journey collects the theme associated with the organization. If the user is not associated with an organization, or the organization has no brand, the journey reverts back to a default theme.
  • If a theme is specified, then the journey sets the theme and continues execution to complete user authentication.
  • The set theme is maintained throughout the user session, including the user profile pages.

Testing the use case

  1. In the Identity Cloud admin UI, go to Journeys.

  2. Click the Login journey you created for brand switching and copy the Preview URL.

    uc_preview_url

  3. Paste the preview URL into a browser using Incognito or Browsing mode.

  4. Enter the username of the test user and click Next.

    The theme associated with the test user’s organization is applied to the journey.

  5. Enter the test user’s password and click Next.

  6. The test user is now signed in.

Video

Here is a short video of the branded journey in action:

uc_brand_video

Conclusion

This use case has demonstrated how themeable hosted pages provide an easy way to communicate a specific brand identity to your customers without requiring heavy UI development.

The flexible Organization model provides brand metadata and the relationship to users, while easy-to-build Intelligent Access journeys can leverage organizational data and dynamically apply a branded user experience.

Additional Resources

Documentation:

Training videos:

Acknowledgements: John Kimble and Andrew Potter

1 Like

Thank you very much for this blog, it’s really useful !
With the last versions of P1AIC you don’t need anymore to have a Get Access Token node, you can user the openidm biding available in next-gen scripts. You can also create Libraries so that you can reused the methods in different scripts.
Below is a code to create a library to request for the theme stored in an Organization:

function getThemeFromUserOrg(uuid,openidm,logger) {
    var user = openidm.read("managed/alpha_user/" + uuid);
    var memberOfOrgIDs = user.memberOfOrgIDs;
    var themeArray= [];
    logger.error("memberOfOrgIDs: "+memberOfOrgIDs);
    for(i = 0; i < memberOfOrgIDs.length; i++) {
        var org = openidm.read("managed/alpha_organization/" + memberOfOrgIDs[i]);
        if ( org.theme && (themeArray.indexOf(org.theme)==-1)) {
            themeArray.push(org.theme);
        }
    };
    return themeArray;
}
exports.getThemeFromUserOrg = getThemeFromUserOrg;

and then in your Select Brand node use this script:

var orgUtilsLib = require('orgUtilsLib');
var uuid = nodeState.get("_id");
logger.error("uuid: " + uuid);
var choiceThemes = orgUtilsLib.getThemeFromUserOrg(uuid,openidm,logger);
nodeState.putShared("choiceThemes", choiceThemes);
outcome = "true";

if (choiceThemes.length >0 && callbacks.isEmpty()) {
    var stage = "themeId="+choiceThemes[0];
    callbacksBuilder.pollingWaitCallback("0", "Please wait ...");
    action.goTo(outcome).withStage(stage);
} else {
    action.goTo(outcome);
}

Special thanks to @david.gwizdala who explained it to me!

2 Likes