Using an Authentication Tree Stage to Build a Custom UI with the ForgeRock JavaScript SDK

The ForgeRock JavaScript SDK greatly simplifies the process of adding intelligent authentication to your applications. It offers a ready-to-use UI that completely handles rendering of authentication steps. You can also take full control and create a custom UI; in which case, it’s helpful to know the current stage of the authentication tree to determine which UI you should render.

ForgeRock Access Management (AM) 7.0 adds a stage property to the page node that can be used for this purpose, and alternate approaches are available for prior versions. This post will show you two approaches for AM 6.5, and one for AM 7.

AM 6.5

While the stage property doesn’t exist in authentication trees prior to AM 7.0, there are two alternate approaches to achieve the same result.

Approach #1: Metadata Callbacks

Using metadata callbacks, you can inject the stage value into the tree’s response payload. The only difference is that the value will appear in a callback, instead of directly associated with the step itself. This approach involves three steps:

  1. Create a script to add the metadata callback.
  2. Update your tree to execute that script.
  3. Read the metadata callback in your application.

Step 1: Add a Metadata Callback Using a Script

  • Create a script of type Decision node script for authentication trees.
  • Give it an appropriate name, such as “MetadataCallback: UsernamePassword”.
  • In the script, add a metadata callback that creates an object with a stageproperty. Be sure to also set the outcome value:
var fr = JavaImporter(
  org.forgerock.json.JsonValue,
  org.forgerock.openam.auth.node.api.Action,
  com.sun.identity.authentication.spi.MetadataCallback
);

with (fr) {
  var json = JsonValue.json({ stage: "UsernamePassword" });
  action = Action.send(new MetadataCallback(json)).build();
}

outcome = "true";

As with all scripts, ensure you have whitelisted any imported classes.

Step 2: Update Your Tree to Execute the Script

Add a scripted decision node to your page node and configure it to reference the script created in the previous step. In this example, the step payload will contain three callbacks:

  • MetadataCallback
  • NameCallback
  • PasswordCallback

Step 3: Read the Metadata Callback

Use the SDK to find the metadata callback and read its stage property:

function getStage(step) {
  // Get all metadata callbacks in the step
  const metadataCallbacks = step.getCallbacksOfType(CallbackType.MetadataCallback);

  // Find the first callback that contains a "stage" value in its data
  const stage = metadataCallbacks
    .map(x => {
      const data = x.getData();
      const dataIsObject = typeof data === "object" && data !== null;
      return dataIsObject && data.stage ? data.stage : undefined;
    })
    .find(x => x !== undefined);

  // Return the stage value, which will be undefined if none exists
  return stage;
}

Approach #2: Inspecting Callbacks

If you have relatively few and/or well-known authentication trees, it’s likely you can determine the stage by simply looking at the types of callbacks in the step.

For example, it’s common for a tree to start by capturing the username and password. In this case, you can inspect the callbacks to see if they consist of a NameCallback and PasswordCallback. If your tree uses WebAuthn for passwordless authentication, the SDK can help with this inspection:

function getStage(step) {
  // Check if the step contains callbacks for capturing username and password
  const usernameCallbacks = step.getCallbacksOfType(CallbackType.NameCallback);
  const passwordCallbacks = step.getCallbacksOfType(CallbackType.PasswordCallback);
  if (usernameCallbacks.length > 0 && passwordCallbacks.length > 0) {
    return "UsernamePassword";
  }

  // Use the SDK to determine if this is a WebAuthn step
  const webAuthnStepType = FRWebAuthn.getWebAuthnStepType(step);
  if (webAuthnStepType === WebAuthnStepType.Authentication) {
    return "DeviceAuthentication";
  } else if (webAuthnStepType === WebAuthnStepType.Registration) {
    return "DeviceRegistration";
  }

  // ... Add checks to determine other stages in your trees ...

  return undefined;
}

AM 7.0 Approach

Using AM 7.0 to specify a stage is straightforward. When constructing a tree, place nodes inside a page node and then specify its stage, which is a free-form text field:

When you use the SDK’s FRAuth module to iterate through a tree, you can now call the getStage() method on the returned FRStep, and decide which custom UI needs to be rendered:

// Get the current step in the tree
const currentStep = await FRAuth.next(previousStep);

// Use the stage value configured in the tree
switch (currentStep.getStage()) {
  case "UsernamePassword":
    // Render your custom username/password UI
    break;
  case "SomeOtherStage":
    // etc
    break;
}

3 Comments
  1. bsebbah 7 days ago

    Hi Jared, thanks for that very interesting article. It is very helpful and way cleaner than the solution that we had found in our 6.5.2.
    If I understand correctly, one would have to create as many metadata callbacks as the number of screens that are managed by the authentication trees.
    It means that if we have a high number of screens (which is our case), we have to deal with a high number of screens.
    What about trying to factorize by using a sharedstate variable that would have to be set before each and every page node? The script would then read the value of the variable and replace UsernamePassword by the value of the variable in this line of the script:
    var json = JsonValue.json({ stage: “UsernamePassword” });

    Thanks!

  2. bsebbah 6 days ago

    So we did this but we simply had to replace:
    var json = JsonValue.json({ stage: “UsernamePassword” });
    by
    var json = JsonValue.json({ stage: current_stage });

    Note that the declaration of the variable current_stage can’t be done like this:
    var current_stage= new String(sharedState.get(“stage_name”));
    but it has to be done like that:
    var current_stage= sharedState.get(“stage_name”).toString();

  3. Scott Heger 3 days ago

    Good stuff!

Leave a reply

©2020 ForgeRock - we provide an identity and access platform to secure every online relationship for the enterprise market, educational sector and even entire countries. Click to view our privacy policy and terms of use.

Log in with your credentials

Forgot your details?