Access Management 7.2.2

Authentication nodes and trees

Authentication trees (also referred to as Intelligent Authentication) provide fine-grained authentication by allowing multiple paths and decision points throughout the authentication flow. Use them to build complex authorization scenarios, while offering a streamlined login experience to users.

Authentication trees are made up of authentication nodes, which define actions taken during authentication. Each node performs a single task during authentication, for example, collecting a username or making a simple decision based on a cookie.

Nodes can have multiple outcomes rather than just success or failure. This allows you to create complex yet customer-friendly authentication experiences by linking nodes together, creating loops, branching the tree for different authentication scenarios, and nesting nodes within a tree:

Create multiple paths for authentication by linking nodes within trees.
Figure 1. Example authentication tree

To further control the authentication process, you can assign authentication levels to branches on a tree, with higher levels being used typically to allow access to more restricted resources.

Authentication trees differ in the following ways from traditional authentication chains:

  • Authentication nodes are not yet available for all the functionality provided by authentication modules.

  • Authentication trees cannot mix with authentication chains. Each authentication to AM can use either a tree or a chain, but not both together.

  • The functionality derived from post-authentication plugins, used traditionally with authentication chains, is handled differently when using trees. For example:

    • Session property management is handled by individual nodes. See Set Session Properties node.

    • Calling out to third-party systems is handled by scripted nodes. See Scripted Decision node.

    • Registering events to make HTTP POST calls to a server is handled by webhooks. See Configure authentication webhooks. Note that post-authentication plugins do not get triggered when authenticating to a tree, only to a chain.

Authentication levels for trees

When a user successfully authenticates, AM creates a session, which allows AM to manage the user’s access to resources. The session is assigned an authentication level. The authentication level is often used as a measure of the strength of the authentication performed. For example, simple username and password may be assigned a low authentication level, whereas multi-factor with Push and webAuthn, a high one.

Authorization policies may require a particular authentication level to access protected resources. When an authenticated user tries to access a protected resource without satisfying the authentication level requirement, AM denies access to the resource and returns an advice indicating that the user needs to reauthenticate at the required authentication level to access the resource.

The web or Java agent or policy enforcement point can then send the user back to AM for session upgrade. For more information, refer to Session upgrade

AM provides the following nodes to manage authentication levels:

  • The Authentication Level Decision node, that checks that the current authentication level is equal or greater than the one specified in the node.

  • The Modify Authentication Level node, that can raise or lower the authentication level.

Position these nodes to alter the authentication level depending on the route take through the authentication tree.

Account lockout for trees

You should limit the number of attempts a user can make at authenticating with credentials. Limiting the number of attempts helps to prevent password-guessing and brute-force attacks.

By default, authentication trees support account lockout and provide nodes for checking and changing a user’s status:

Account Active Decision node

Use this node to determine if an account is active or inactive (locked).

Account Lockout node

Use this node to change the user’s status to active or inactive (locked).

When setting an account to active, the node also resets the failed attempts and lockout duration counters.

In addition to the lockout-specific nodes above, the Success and Failure nodes include account lockout functionality, when lockout is enabled in a realm, as follows:

Success node
  • Checks the User Status property of the user profile, when reached, and fails the authentication with an error message, if the account is marked as Inactive:

    Account locked error when reaching Success node.

    The error message is returned in the JSON response if authenticating to the tree by using REST:

    {
        "code":401,
        "reason":"Unauthorized",
        "message":"User Locked Out."
    }
  • Resets the failure count in the user profile, when reached, if the User Status property is set to Active.

Failure node
  • Checks the invalid attempts property of the user profile, and returns a warning message if the number of failed attempts is equal to or greater than the configured Warn User After N Failures value in the realm:

    Invalid attempts limit warning when reaching Failure node.

    The error message is returned in the JSON response if authenticating to the tree by using REST:

    {
        "code":401,
        "reason":"Unauthorized",
        "message":"Warning: You will be locked out after 1 more failure(s)."
    }
  • Increments the failure count in the user profile, when reached.

  • Returns an error message if the account is marked as Inactive:

    Account locked error when reaching Failure node.

    The error message is returned in the JSON response if authenticating to the tree by using REST:

    {
        "code":401,
        "reason":"Unauthorized",
        "message":"User Locked Out."
    }

For information on configuring account lockout in a realm, refer to Account lockout.

Specify IDM identity resources in trees

When running AM as part of an integrated platform with IDM, trees configured to use the platform need to identify the type of identity resource or object the tree is working with. To do this, use the identityResource configuration property. If the property is not included in the tree configuration, it defaults to managed/user.

To update identityResource on a tree, use the REST API to update the tree:

$ curl \
 --request PUT \
 --header 'Accept-API-Version: protocol=2.1,resource=1.0' \
 --header 'Accept: application/json' \
 --header 'If-None-Match: *' \
 --header 'Content-Type: application/json' \
 --header 'Cookie: <omitted for length>' \
 --data '{
   "entryNodeId":"e301438c-0bd0-429c-ab0c-66126501069a",
   "nodes":{},
   "staticNodes":{},
   "description":"Example tree description",
   "identityResource":"managed/newObjectType"
 }' \
 "https://default.iam.example.com/am/json/realms/root/realm-config/authentication/authenticationtrees/trees/ExampleTree"

In the above example, the tree ExampleTree has no nodes added to it yet. It includes the identityResource property, set to use a managed object in IDM called newObjectType.

Because this is a PUT request, you must include the entire tree as part of the request. For more information about using the REST API, refer to REST in AM.

Configure authentication trees

The following table summarizes the high-level tasks required to configure authentication trees:

Task Resources

Design your user authentication journey

Authentication trees are very flexible. For example, the same tree can branch for different use cases, or users can be forced to loop though branches until they are able to present the required credentials.

It is easy to create a massive tree that is difficult to understand, read, and maintain in the UI. For this reason, AM allows you to nest trees within trees.

The best way to tackle the design decision is to write down a list of required steps users would need to take to log in to your environment, and then check the list of nodes available in AM.

Evaluation installs of AM that use the embedded data store provide ready-made sample authentication trees to demonstrate how they can be put together.

These sample trees are not installed by default in instances of AM that use an external configuration store, or if you are upgrading an existing instance of AM. To obtain a copy of the sample trees that you can import into your instance, see How do I access and build the sample code provided for AM/OpenAM (All versions)? in the ForgeRock Knowledge Base.

For information on importing the sample tree JSON files by using Amster, see Import configuration data in the Amster documentation.

Decide if you need custom authentication nodes and webhooks

If the nodes available in AM or in the ForgeRock Marketplace do not suit your needs, you can build your own nodes.

In the same way, you can create custom webhooks for nodes that need them.

Configure your authentication trees

Use the authentication tree designer to put together your trees quickly.

Configure webhooks, if required

If you have configured the Register Logout Webhook node, configure its webhook.

Create an authentication tree

  1. In the AM admin UI, go to Realms > Realm Name > Authentication > Trees and click Create Tree.

  2. Enter a tree name, for example myAuthTree, and click Create.

    The authentication tree designer is displayed, with the start entry point connected to the failure exit point.

    The authentication tree designer provides the following features on the toolbar:

    Authentication tree designer toolbar
    Button Usage
    icon-trees-auto-layout

    Lay out and align nodes according to the order they are connected.

    icon-trees-full-screen

    Toggle the designer window between normal and full screen layout.

    icon-trees-delete-node

    Remove the selected node. Note that the Start entry point cannot be deleted.

  3. Add a node to the tree by dragging the node from the Components panel on the left-hand side, and dropping it into the designer area.

    The list of authentication nodes is split into a number of categories. Click the category title to expand and collapse the categories.

    Use the filter text field to restrict the list of authentication nodes, which will match on the nodes' name, and any tags applied to the node:

    Filters match on the nodes' name, and any tags applied to the node.
  4. Configure the node properties by using the right-hand panel.

    For more information on the available properties for each node, see Authentication nodes configuration reference.

  5. Connect the node to the tree as follows:

    1. Select and drag the output connector from an existing node and drop it onto the new node.

    2. Select and drag the output connector from the new node and drop it onto an existing node.

    Nodes have one or more connectors, displayed as dots on the node. Unconnected connectors are colored red and must be connected to other nodes in the tree.

    Input connectors appear on the left of the node, output connectors appear on the right.

    A line is drawn between the connectors of connected nodes, and the connectors will no longer be red.

  6. To change a connection, select and drag the green connector in the connection and drop it onto the new location.

  7. Continue adding, connecting and removing nodes until the tree is complete, then click Save.

  8. Test your authentication tree by navigating to a URL similar to the following: https://openam.example.com:8443/openam/XUI/?realm=/alpha&service=myAuthTree#login

Enable and disable an authentication tree

Custom authentication trees are enabled by default, when they are saved. For security purposes, you can disable custom authentication trees during development and testing, to prevent accidentally allowing access through these trees. Rather than having unused authentication trees enabled, you should disable the default authentication trees until you need them.

When a user attempts to authenticate through a disabled tree, AM returns a Tree does not exist error.

To enable or disable an authentication tree, set the enabled flag in the tree configuration:

Over REST, send a PUT request to update the tree configuration. You must specify the tree ID and the nodes in the tree. This example disables the myAuthTree created previously:

$ curl \
--header "Content-Type: application/json" \
--header 'Cookie: iPlanetDirectoryPro=<cookie>' \
--header 'accept-api-version: protocol=2.1,resource=1.0' \
--header "If-Match: *" \
--request PUT \
--data '
  {
  "entryNodeId": "c11e9cf8-ef48-4740-876f-6300e2f46aef",
  "nodes": {
    "c11e9cf8-ef48-4740-876f-6300e2f46aef": {
      "displayName": "Page Node",
      "nodeType": "PageNode",
      "x": 147,
      "y": 25,
      "connections": {
        "outcome": "15839e1c-5085-4f58-bc94-c4cc848a0ae8"
      }
    },
    "15839e1c-5085-4f58-bc94-c4cc848a0ae8": {
      "displayName": "Data Store Decision",
      "nodeType": "DataStoreDecisionNode",
      "x": 349,
      "y": 25,
      "connections": {
        "true": "70e691a5-1e33-4ac3-a356-e7b6d60d92e0",
        "false": "e301438c-0bd0-429c-ab0c-66126501069a"
      }
    }
  },
  "enabled": false
}' \
"https://openam.example.com:8443/openam/json/realms/root/realms/alpha/realm-config/authentication/authenticationtrees/trees/myAuthTree"
{
  "_id": "myAuthTree",
  "_rev": "2070284866",
  "uiConfig": {},
  "entryNodeId": "c11e9cf8-ef48-4740-876f-6300e2f46aef",
  "nodes": {
    "c11e9cf8-ef48-4740-876f-6300e2f46aef": {
      "displayName": "Page Node",
      "nodeType": "PageNode",
      "x": 147,
      "y": 25,
      "connections": {
        "outcome": "15839e1c-5085-4f58-bc94-c4cc848a0ae8"
      }
    },
    "15839e1c-5085-4f58-bc94-c4cc848a0ae8": {
      "displayName": "Data Store Decision",
      "nodeType": "DataStoreDecisionNode",
      "x": 349,
      "y": 25,
      "connections": {
        "true": "70e691a5-1e33-4ac3-a356-e7b6d60d92e0",
        "false": "e301438c-0bd0-429c-ab0c-66126501069a"
      }
    }
  },
  "staticNodes": {
    "startNode": {
      "x": 50,
      "y": 25
    },
    "70e691a5-1e33-4ac3-a356-e7b6d60d92e0": {
      "x": 570,
      "y": 30
    },
    "e301438c-0bd0-429c-ab0c-66126501069a": {
      "x": 573,
      "y": 107
    }
  },
  "enabled": false
}

Configure authentication webhooks

Webhooks are used to send HTTP POST calls to a server with contextual information about an authentication session when a predefined event occurs, for example, logging out.

Webhooks are used from within authentication trees by the Register Logout Webhook node.

Create a webhook

  1. In the AM admin UI, go to Realms > Realm Name > Authentication > Webhooks.

    1. To create a new webhook, select Create Webhook, specify a webhook name, and click Create.

    2. To edit an existing webhook, select the name of the webhook.

    Creating a new authentication webhook.
  2. Complete the fields as required:

    Url

    Specifies the URL to which the HTTP POST is sent when the event occurs.

    Body

    Specifies the body of the HTTP POST. You can send different formats by also setting the correct Content-Type header in the Header property, for example:

    • Form Data. Enter the body value in the format parameter=value&parameter2=value2, and set a Content-Type header of application/x-www-form-urlencoded.

    • JSON Data. Enter the body value in the format {"parameter":"value","parameter2":"value2"}, and set a Content-Type header of application/json.

    Headers

    Specifies any HTTP headers to add to the POST.

    To add a header, enter the name of the header in the Key field, and the value, and click Add (➕).

    To remove a header, click Delete (✖).

    The fields in a webhook support variables for retrieving values from the user’s session after successfully authenticating.

    Specify a variable in the following format:

    ${variable_name}

    To access the type of webhook event, use the WebhookEventType parameter key to return one of the following possible values:

    • LOGOUT

    • UPGRADE

    • DESTROY

    • MAX_TIMEOUT

    • IDLE_TIMEOUT

    For example, to retrieve the event type as a query parameter: &event=${WebhookEventType}

    You can use a variable to access custom properties added to the session with the Set Session Properties node as well as the following default session properties:

    Default Session Properties
    Property Example value Description

    AMCtxId

    22e73c81-708e-4849-b064-db29b68ef943-105372

    The audit ID for the session. This is logged as the trackingIds field in AM access audit logs.

    amlbcookie

    01

    The cookie that identifies the AM server that generated the session. For environments with multiple AM servers, this can be used for load balancer stickiness.

    authInstant

    2022-02-28T14:06:31Z

    The exact time that authentication completed.

    AuthLevel

    5

    The authentication level of the session, determined by the login mechanism used to create the session. For example, a tree can have an authentication level of 10.

    Step-up authentication is triggered if an authentication level specified by an agent or policy that is designed to protect a resource, is greater than or equal to the value of the AuthLevel session property.

    For more information, see Session upgrade.

    AuthType

    DataStore

    A pipe-separated list of modules to which the user has authenticated. For example, module1|module2|module3(authentication chains only).

    CharSet

    UTF-8

    The character set for the session, set to UTF-8.

    clientType

    genericHTML

    The type of client, set to genericHTML.

    FullLoginURL

    /openam/XUI/?realm=%2Falpha

    The full login URL, including query parameters.

    Host

    192.0.2.1

    The originating IP address of the authentication request (authentication trees only).

    HostName

    192.0.2.1

    The host name that was used when the session was authenticated.

    IndexType

    service

    Based on the value of the authIndexValue query parameter during authentication. Typically, this is set to service.

    Locale

    en_US

    The session locale.

    loginURL

    /openam/XUI

    The base login URL. A subset of FullLoginURL.

    OidcSid

    g0wmSpoAIwH6HAwCnurvRcfYqh4

    Unique session ID used by AM to determine whether OIDC ID tokens granted for the same client relate to the same session. This appears when Enable Session Management (storeOpsToken) is set to true in the OAuth 2.0 provider settings.

    Organization

    o=alpha,ou=services,dc=openam,dc=forgerock,dc=org

    The DN of the realm where authentication took place.

    Principal

    id=demo,ou=user,o=alpha,ou=services,dc=openam,dc=forgerock,dc=org

    The value of sun.am.UniversalIdentifier.

    Principals

    demo

    The username of the user. For multiple principals, this can be a pipe-separated list (authentication chains only).

    Service

    ldapService

    The name of the tree or chain that was used to authenticate this session.

    successURL

    /openam/console

    The URL that was redirected to, upon a successful login request.

    sun.am.UniversalIdentifier

    id=demo,ou=user,o=alpha,ou=services,dc=openam,dc=forgerock,dc=org

    The DN of the user (username is lowercase).

    UserId

    demo

    The id value from the Principal property.

    UserProfile

    Required

    Can be one of: Required, Create, Ignore, or CreateWithAlias. Based on the value of the dynamicProfileCreation authentication configuration. Values other than Ignore indicates that user profile attributes were mapped based on the User Attribute Mapping to Session Attribute setting. See authentication configuration for details.

    Default: Required.

    UserToken

    demo

    The username, as defined in the Principals property. For authentication chains, this is the last principal value.

Example webhook

The following figure shows an example webhook using variable substitutions:

Example authentication webhook.

Specifying a variable that is not present in the user’s session places the literal variable text in the HTTP POST, for example user=${UserId}, rather than user=demo.

Customize authentication trees

Your deployment might require customizing standard authentication trees.

For information on customizing authentication nodes, refer to Node development.

Create post-authentication hooks for trees

This section explains how to create a hook used by a node within an authentication tree. These tree hooks can perform custom processing after an authentication tree has successfully completed, and a session created.

AM includes the following authentication tree hooks:

CreatePersistentCookieJwt

Used by the SetPersistentCookieNode authentication node.

UpdatePersistentCookieJwt

Used by the PersistentCookieDecisionNode authentication node.

The core class of an authentication tree hook

The following example shows the UpdatePersistentCookieTreehook class, as used by the Persistent Cookie Decision node:

package org.forgerock.openam.auth.nodes.treehook;

import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

import org.forgerock.guice.core.InjectorHolder;
import org.forgerock.http.protocol.Cookie;
import org.forgerock.http.protocol.Request;
import org.forgerock.http.protocol.Response;
import org.forgerock.openam.auth.node.api.TreeHook;
import org.forgerock.openam.auth.node.api.TreeHookException;
import org.forgerock.openam.auth.nodes.PersistentCookieDecisionNode;
import org.forgerock.openam.auth.nodes.jwt.InvalidPersistentJwtException;
import org.forgerock.openam.auth.nodes.jwt.PersistentJwtStringSupplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.assistedinject.Assisted;

/**
 * A TreeHook for updating a persistent cookie.
 */
@TreeHook.Metadata(configClass = PersistentCookieDecisionNode.Config.class) (1)
public class UpdatePersistentCookieTreeHook implements TreeHook { (2)

    private final Request request;
    private final Response response;
    private final PersistentCookieDecisionNode.Config config;
    private final PersistentJwtStringSupplier persistentJwtStringSupplier;
    private final PersistentCookieResponseHandler persistentCookieResponseHandler;
    private final Logger logger = LoggerFactory.getLogger("amAuth");

    /**
     * The UpdatePersistentCookieTreeHook Constructor.
     *
     * @param request The request.
     * @param response The response.
     * @param config the config for updating the cookie.
     */
    @Inject (3)
    public UpdatePersistentCookieTreeHook(@Assisted Request request, @Assisted Response response,
                                          @Assisted PersistentCookieDecisionNode.Config config) {
        this.request = request;
        this.response = response;
        this.config = config;
        this.persistentJwtStringSupplier = InjectorHolder.getInstance(PersistentJwtStringSupplier.class);
        this.persistentCookieResponseHandler = InjectorHolder.getInstance(PersistentCookieResponseHandler.class);
    }

    @Override
    public void accept() throws TreeHookException { (4)
        logger.debug("UpdatePersistentCookieTreeHook.accept");
        String orgName = PersistentCookieResponseHandler.getOrgName(response);
        Cookie originalJwt = getJwtCookie(request, config.persistentCookieName());
        if (originalJwt != null) {
            String jwtString;
            try {
                jwtString = persistentJwtStringSupplier.getUpdatedJwt(originalJwt.getValue(), orgName,
                        String.valueOf(config.hmacSigningKey()), config.idleTimeout().to(TimeUnit.HOURS));
            } catch (InvalidPersistentJwtException e) {
                logger.error("Invalid jwt", e);
                throw new TreeHookException(e);
            }

            if (jwtString != null && !jwtString.isEmpty()) {
                persistentCookieResponseHandler.setCookieOnResponse(response, request, config.persistentCookieName(),
                        jwtString, originalJwt.getExpires(), config.useSecureCookie(), config.useHttpOnlyCookie());
            }
        }
    }

    private Cookie getJwtCookie(Request request, String cookieName) {
        if (request.getCookies().containsKey(cookieName)) {
            List<Cookie> cookies = request.getCookies().get(cookieName);
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(cookieName)) {
                    return cookie;
                }
            }
        }
        return null;
    }
}
1 The @TreeHook.Metadata annotation. Before defining the core class, use a Java @TreeHook.Metadata annotation to specify the class the tree hook uses for its configuration. Use the configClass property to specify the configuration class of the node that will be using the tree hook.

The node class must invoke ActionBuilder's addSessionHook method to specify the treehook class to be run after a successful login.

For example, in the PersistentCookieDecisionNode.class:

@Override
public Action process(TreeContext context) throws NodeProcessException {
    ...
    actionBuilder = actionBuilder
        .addSessionHook(UpdatePersistentCookieTreeHook.class, nodeId, getClass().getSimpleName());
}
2 The core class must implement the TreeHook interface.

For more information, see the TreeHook interface in the AM Public API Javadoc.

3 AM uses Google’s Guice dependency injection framework for authentication nodes and tree hooks. Use the @Inject annotation to construct a new instance of the tree hook, specifying the configuration interface set up earlier and any other required parameters. For more information, see the Inject annotation type and the Assisted annotation type in the Google Guice Javadoc.
4 Creating an Accept instance. The main logic of a tree hook is handled by the Accept function.