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:
- Create a script to add the metadata callback.
- Update your tree to execute that script.
- 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 theoutcome
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
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;
}