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:
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
: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:
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
: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.
|
|
||
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 |
Create an authentication tree
-
In the AM admin UI, go to Realms > Realm Name > Authentication > Trees and click Create Tree.
-
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 Lay out and align nodes according to the order they are connected.
Toggle the designer window between normal and full screen layout.
Remove the selected node. Note that the Start entry point cannot be deleted.
-
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:
-
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.
-
Connect the node to the tree as follows:
-
Select and drag the output connector from an existing node and drop it onto the new node.
-
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.
-
-
To change a connection, select and drag the green connector in the connection and drop it onto the new location.
-
Continue adding, connecting and removing nodes until the tree is complete, then click Save.
-
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
-
In the AM admin UI, go to Realms > Realm Name > Authentication > Webhooks.
-
To create a new webhook, select Create Webhook, specify a webhook name, and click Create.
-
To edit an existing webhook, select the name of the webhook.
-
-
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¶meter2=value2
, and set aContent-Type
header ofapplication/x-www-form-urlencoded
. -
JSON Data. Enter the body value in the format
{"parameter":"value","parameter2":"value2"}
, and set aContent-Type
header ofapplication/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/UI/Login?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 toservice
.Locale
en_US
The session locale.
loginURL
/openam/UI/Login
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 thePrincipal
property.UserProfile
Required
Can be one of:
Required
,Create
,Ignore
, orCreateWithAlias
. Based on the value of thedynamicProfileCreation
authentication configuration. Values other thanIgnore
indicates that user profile attributes were mapped based on theUser 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.
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.
|
||
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. |