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.0

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.

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 stage property. 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.

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

image

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

// 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;
}
1 Like

Posted by bsebbah

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!

Posted by bsebbah

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

Posted by jeyasree

How to display drop down in custom node for example Data type dropdown contains Text,Number,Email,Phone number…

Jatinder Singh

You can try the following snippet in your custom node:

public interface Config {
        @Attribute(order = 100,  validators = {RequiredValueValidator.class} )
        default DataType dataType() { return DataType.Text; }
 }

 public enum DataType {
   Text,
   Number,
   Email,
   PhoneNumber
 }