Use case: Configure Terms & Conditions (T&Cs) for an organization in ForgeRock Identity Cloud

Use case overview

Providing different Terms & Conditions (T&Cs) for groups of users depending on their needs is a common use case that can be addressed in ForgeRock Identity Cloud.

There are many situations where you may wish to present different T&Cs to different user groups. For example, based on the user’s locale, the application, the realm, or the organization to which the user belongs. In this example use case, we’ll focus on providing T&Cs based on the user’s organization. This is easily achieved using the flexible Organization model and Journeys capabilities of Identity Cloud.

We’ll demonstrate how to configure an organization with multiple T&Cs versions. We’ll then create a user journey that determines which organization the user belongs to, retrieves the appropriate active T&Cs for the organization, and prompts the user to accept or deny the T&Cs when logging in. If the user accepts the T&Cs presented, the version accepted will be stored in their profile.

Steps to achieve this use case

The use case has four stages:

  1. Add a T&C property to the organization managed object

  2. Create T&Cs versions for an organization

  3. Create the scripts for the user journey

  4. Create the user journey

Prerequisites and assumptions

  • We have already created a Get Access Token node, which will be used to acquire the access token required to interact with Identity Management services in Identity Cloud.

    See How do I acquire an access token for Identity Cloud API calls in a Scripted Decision node? for further information.

  • We have configured an organization called Company A. See Organizations for further information on organizations in Identity Cloud.

  • A test end user has been created in the identity repository.

  • The end user for this use case is assumed to be a member of only one organization (Company A).

  • Once the end user has accepted the organization’s T&Cs, this will NOT be checked by the provided script at subsequent login. This means that the end user will always be prompted to accept the currently active version of the T&Cs each time they log in. It is a reader exercise to extend this approach so that a user is only prompted to accept the currently active version of T&Cs at subsequent login if they haven’t yet already done so.

  • For brevity, and to focus on the specific use case, the sample journey does not perform registration or authentication of the user. A real world application of this would typically require the user to enter some credentials to be validated, or the user to be created, prior to accessing the T&Cs.

  • T&Cs are stored and presented in a single language. There is no locale support in this approach.

Add a T&C property to the organization managed object

In this example, we’ll add a T&C version property to the alpha_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.

  5. Add the Terms & Conditions property with the following details:

    • Property Name: tNc, Label: Terms & Conditions, Type: Object

  6. Click the Edit icon next to the tNc property.

  7. Add a version array:

    • Property Name: version, Label: Version, Type: Array

  8. Click the Edit icon next to the version array property.

  9. Change the Item Type to Object and add the following properties:

    • Property Name: Version, Label: Version, Type: Number
    • Property Name: TAndC, Label: Terms & Conditions, Type: String
    • Property Name: Active, Label: Active, Type: Boolean

  10. Click Save.

Create T&Cs versions for the organization

In this example, we’ll create T&C versions for an organization called Company A.

  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 Identities > Manage > Alpha realm - Organizations.

  3. Click on the organization that you want to add T&Cs to. In our example, we’re using Company A.

  4. Click Terms & Conditions.

  5. Click + to add two T&C versions; version 1 and 2. The text you enter in the TAndC fields is the terms and conditions that will be displayed to end users.

  6. Set Version 2 as Active.

  7. Click Save.

Create the scripts for the user journey

You will need to create three scripts for the user journey:

  • GetTnC
  • GetTnCFromOrg
  • SaveT&C

GetTnC

Create the following GetTnC script. This script retrieves the active T&Cs for the organization that the user is a member of.

/*
 * This sample script retrieves the active Terms and Conditions for the organization that the user is a member of.  
 * The Organization Name, Terms/Conditions and Version is added to shared state for later use
 * The user's currently accepted T&Cs for the organisation is NOT checked by this script.
 *
 *
 * A user is assumed to be a member of 1 and only 1 organization.
 * The organization data model must have been updated to include the `tNc` object property
 * The organization must have one active tNc in its data.
 * This script assumes managed/alpha_organization is being used
 *
 * Requires following ESVs:
 * 1. esv.tenant.env.fqdn - for example: openam-mycompany-mytenant-usw1.id.forgerock.io
 *
 * Assumes a valid access token with correct scopes already exist in shared state in the nodeConfig.accessTokenStateField variable
 * See: https://backstage.forgerock.com/knowledge/kb/article/a20028489 on how to acquire the access token
 */

var nodeConfig = {
  nodeName: "GetTandC",
  tenantFqdnEsv: "esv.tenant.env.fqdn",
  accessTokenStateField: "idmAccessToken"
};

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

var accessToken;
var tenantFqdn;
var retVal = "";

function getTandC(){
  try {
      var user = nodeState.get("username");
      var _id = nodeState.get("_id").asString();
      var theOrg = idRepository.getAttribute(_id, "fr-idm-managed-user-memberoforgid");
      nodeState.putShared("theOrg_id", theOrg.toString());

      var IDM_API_TOKEN = accessToken;

  // IDM API Configuration
      var IDM_API_URI = "https://" + tenantFqdn + "/openidm/managed/alpha_organization/" + theOrg.toArray()[0] + "?_fields=*,parent/_id,parent/name,parent/description";
    	
      nodeLogger.debug("IDM API call: " + IDM_API_URI + "; _id: " + _id + "; OrgId: " + theOrg + "; AccessToken: " + IDM_API_TOKEN);
    
      var loginDoRequest = new org.forgerock.http.protocol.Request();
      loginDoRequest.setMethod('GET');
      loginDoRequest.setUri(IDM_API_URI);
      loginDoRequest.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
      loginDoRequest.getHeaders().add("Authorization", "Bearer " + IDM_API_TOKEN);

      var response = httpClient.send(loginDoRequest).get();
      nodeLogger.debug("Response from IDM: " + response.getEntity().getString());
      var result = JSON.parse(response.getEntity().getString());
      nodeState.putShared("theOrgName", result.name);
      nodeLogger.debug("Org Name: " + result.name);
    
      for (var x = 0; x < result.tNc.version.length; x++){
          if (result.tNc.version[x].Active){
              retVal = result.tNc.version[x].TAndC;
              nodeState.putShared("theVersion", result.tNc.version[x].Version);
              x = result.tNc.version.size;
          }
      }
      nodeState.putShared("theTNC", retVal);
 	  nodeLogger.debug("Outcome: SUCCESS");
      outcome = nodeOutcomes.SUCCESS;
  } catch (e) {
      retVal = e.toString();
      outcome = nodeOutcomes.ERROR;
  }
}

(function () {
  try {
    nodeLogger.debug("Node starting");

    accessToken = nodeState.get(nodeConfig.accessTokenStateField).asString();

    if (!accessToken) {
      nodeLogger.debug("Access token not found in shared state variable:" + nodeConfig.accessTokenStateField);
      action = javaImports.Action.goTo(nodeOutcomes.ERROR).build();
      return;
    }
    
    nodeLogger.debug("Access Token: " + accessToken);

    tenantFqdn = systemEnv.getProperty(nodeConfig.tenantFqdnEsv);
    if (!tenantFqdn) {
      nodeLogger.error("Couldn't get FQDN from esv " + config.tenantFqdnEsv);
      action = javaImports.Action.goTo(nodeOutcomes.ERROR).build();
      return;
    }
    
    nodeLogger.debug("FQDN: " + tenantFqdn);
    
    outcome = nodeOutcomes.ERROR;
    getTandC()
  } catch (e) {
    nodeLogger.error("Exception encountered " + e);
    action = javaImports.Action.goTo(nodeOutcomes.ERROR).build();
    return;
  }
})();

Accept or Deny

Create the following GetTnCFromOrg script. This script prompts the user to accept or deny the T&Cs.


config = {
    "message": {"en-US": nodeState.get("theTNC").asString()},
    "messageYes": {"en-US": "Accept"},
    "messageNo": {"en-US": "Decline"}
};

Save T&C

Create the following SaveT&C script. This script saves the T&Cs with the user identity.

/*
  - 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".
 */

try {
    var theOrgName = nodeState.get("theOrgName").asString();
    var theVersion = nodeState.get("theVersion").toString()

    var tNc = theOrgName + "|" + theVersion + "|" + new Date();

    var username = nodeState.get("_id").asString();
    var attribute = "fr-attr-str1";
    idRepository.setAttribute(username, attribute, [tNc]);
  
} catch (e) {
    nodeState.putShared("Problem saving T&C", e.toString());
}

outcome = "true";

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. Build a journey similar to this:

    Node descriptions:

    • Platform Username - Prompts the user to enter their username. See Platform Username node for further information.
    • Identity Existing User - Checks the username against the identity repository. If that fails, the user is prompted to re-enter their username. See Identify Existing User node for further information.
    • Get IDM 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.
    • Get Active T&C for user’s Org - This Scripted Decision node retrieves the active T&Cs for the organization that the user is a member of. See Step 5 for further information.
    • Page Node - This Page node contains the Accept or Deny node and displays a message to the user. See Step 6 for further information.
    • Accept or Deny - This Configuration Provider node allows the user to accept or deny the T&Cs. See Step 7 for further information.
    • Save T&C - This Scripted Decision node flags the user’s identity as having accepted the T&Cs. See Step 8 for further information.
  5. Click the Get Active T&C for user’s Org node and configure the node as follows:

    • Script: Select the GetTnC script you created previously.
    • Outcomes: Enter success and error.
    • Script Inputs: Add idmAccessToken.

  6. Click the Page Node and add a page description similar to this:
    Accept your organization's T&Cs.

  7. Click the Accept or Deny node, select the GetTnCFromOrg script you created previously, and select Message Node as the Node Type.

    uc_accept_deny_node

  8. Click the SaveT&C node, select the SaveT&C script you created previously, and add true to the Outcomes.

  9. Click Save to save the Journey.

Testing the use case

To test the use case, make sure that your test end user is a member of Company A and does not belong to any other organizations.

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

  2. Click the journey you created previously and copy the Preview URL.

    uc_preview_url

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

  4. Enter the test user’s username in the Sign In screen and click Next.

    uc_tnc_username

    The organization’s currently active T&Cs are displayed.

  5. Click Accept to accept the T&Cs.

    uc_tnc_accept

Conclusion

This simple use case has demonstrated how to present different T&Cs to different user groups based on the user’s organization. This is easily achieved using the flexible Organization model and Journeys capabilities of Identity Cloud.

Additional resources

Documentation:

Training videos:

Acknowledgments: Andrew Potter

2 Likes

@lucy.billington - This is insightful!

Use Case: I just need to use multiple links on T&C page as shown in image and do not need to use the OOTB content+link. Shall I also follow something similar to above or it can be done even easily without doing above steps. And it should also prompt for new T&C version to be accepted if updated.

The below image I achieved by doing below steps:

  1. Added a Page Node with HTML content visible in image in Description and inside it Accept T&C node.
  2. Using Rest API and locale endpoint - I updated OOTB

“login”: {
“login”: {
“agreeToTerms”: " ", //Space Character
“termsAndConditions”: " ", //Space Character
“next”: “Next”
},

But this approach has 1 Look&Feel related issue:
There a dot (by default - may be a bug) from OOTB setup of content which even can not be removed from Rest API UI locale endpoint

Do you have any idea how can we remove this unnecessary dot(above next button) from here?

For your use case, I wouldn’t use the organization based T&Cs described in this article.

You could, potentially, use markdown in the OOTB T&Cs templates to get the links to the documents you want, but these would appear in the popup (I’m assuming you’re using the hosted pages for this), and it looks like you’re trying to avoid the popup.
And this would be helpful to know - what’s the experience you’re trying to achieve? Is the popup a problem?

If you want exactly the experience you have presented, then unfortunately it’s not easy to get rid of the dot. It looks to be added by default by the UI. You should certainly log a ticket to get the logic changed so that it is not automatically added. Or better still, and enhancement for better flexibility of the T&Cs text display.
One thing that I wondered - do you now have a link to the T&Cs popup in that ‘space’ before that dot in the image?

Other potential options include clientside javascript to manipulate the html.
Warning: Just because you can, doesn’t necessarily mean that you should… but here goes…!

Add a scripted node to the page node. The configured script should inject client side javascript to look for the element that contains the offending text and change it. An example script is this:

var fr = JavaImporter(
  org.forgerock.openam.auth.node.api.Action,
  com.sun.identity.authentication.callbacks.ScriptTextOutputCallback
);

var script = "";
script = script.concat("const elems = document.getElementsByTagName('small');");
script = script.concat("if (elems.length > 0) {");
script = script.concat("  console.log(elems[0].innerText);");
script = script.concat("  elems[0].innerText = 'Great';");
script = script.concat("  return;");
script = script.concat("}");

action = fr.Action.send(
    new fr.ScriptTextOutputCallback(
        script
    )
).build();

This will change the content of the element, that by default shows: ‘By clicking ‘Next’ you agree to our Terms & Conditions.’, to ‘Great’

It’s potentially fragile because there may be more than one ‘small’ element on the page, and the UI might change the element it uses there. So this is definitely not an officially supported approach. But, it might just do the job whilst you wait for that enhancement that I suggested you logged :slight_smile:

3 Likes

It is also worth to mention than in ForgeRock Identity cloud, you have no control over release management, and as such this (unsupported) approach is a gamble. On an on-prem deployment, you’re an in control of upgrades, and therefore can mitigate incompatibilities BEFORE it reaches production.
And, finally, the best approach to control the entire user experience is to develop the front end UI, as many of ForgeRock customers are doing.

Regards
Patrick

1 Like