PingAM 7.5.0

Authentication modules and chains

AM uses authentication modules to handle different ways of authenticating. Basically, each authentication module handles one way of obtaining and verifying credentials. You can chain different authentication modules together. In AM, this is called authentication chaining. Each authentication module can be configured to specify the continuation and failure semantics with one of the following four criteria: requisite, sufficient, required, or optional.

Authentication modules in a chain can assign a pass or fail flag to the authorization request. To successfully complete an authentication chain at least one pass flag must have been achieved, and there must be no fail flags.

Flags are assigned when completing a module as shown in the table below:

Authentication Criteria, Flags, and Continuation Semantics
Criteria Fail Pass Example

Requisite

Assigns fail flag.

Authentication chain fail flag.

Exits chain.

Assigns pass flag.

Authentication chain pass flag.

Continues chain.

Active Directory, Data Store, and LDAP authentication modules are often set as requisite because of a subsequent requirement in the chain to identify the user.

For example, the Device ID (Match) authentication module needs a user’s ID before it can retrieve information about the user’s devices.

Sufficient

Assigns no flag.

Continues chain.

Assigns pass flag.

Authentication chain pass flag.

Exits chain.

You could set Windows Desktop SSO as sufficient, so authenticated Windows users are let through, whereas web users must traverse another authentication module, such as one requiring a username and password.

One exception is that if you pass a sufficient module after having failed a required module, you will continue through the chain and will not exit at that point. Consider using a requisite module instead of a required module in this situation.

Required

Assigns fail flag.

Authentication chain fail flag.

Continues chain.

Assigns pass flag.

Authentication chain pass flag.

Continues chain.

You could use a required module for login with email and password, so that it can fail through to another module to handle new users who have not yet signed up.

Optional

Assigns no flag.

Continues chain.

Assigns pass flag.

Authentication chain fail flag.

Continues chain.

You could use an optional module to assign a higher authentication level if it passes. Consider a chain with a requisite Data Store module and an optional Certificate module. Users who only passed the Data Store module could be assigned a lower authentication level than users who passed both the Data Store and Certificate modules. The users with the higher authentication level could be granted access to more highly-secured resources.

In authentication chains with a single module, requisite and required are equivalent. For authentication chains with multiple modules, use required only when you want the authentication chain to continue evaluating modules even after the required criterion fails.

The AM authentication chain editor displays the flags that could be assigned by each module in the chain, and whether execution of the chain continues downwards through the chain or exits out, as shown below:

The authentication chain editor shows the flags each module could assign, and whether they exit the chain or continue.
Figure 1. Authentication Chain with Each Criteria

With AM, you can further set authentication levels per module, with higher levels being used typically to allow access to more restricted resources. The AM SPIs also let you develop your own authentication modules, and post-authentication plugins. Client applications can specify the authentication level, module, user, and authentication service to use among those you have configured. As described later in this guide, you can use realms to organize which authentication process applies for different applications or different domains, perhaps managed by different people.

Authentication levels for chains

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, which is calculated to be the highest authentication level of any authentication module that passed. If the user’s session does not have the appropriate authentication level, then the user may need to reauthenticate again at a higher authentication level to access the requested resource.

The authentication level sets the level of security associated with a module. Typically, the strongest form of authentication is assigned the highest authentication level.

If an authentication chain contains requisite or required modules that were not executed due to the presence of a passing sufficient module in front of them, the session’s authentication level is calculated to be whichever is greater: the highest authentication level of any authentication module that passed, or the highest authentication level of requisite or required modules that were not executed.

You can modify AM’s default behavior, so that a session’s authentication level is always the highest authentication level of any authentication module that passed, even if there are requisite or required modules in the authentication chain that were not executed.

To modify the default behavior, set the org.forgerock.openam.authLevel.excludeRequiredOrRequisite property to true under Deployment > Servers > Server Name > Advanced and restart the AM server.

Authorization policies may also 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.

Configure authentication chains

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

Task Resources

Design the authentication journey of your users

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 modules available in AM.

You can find information on implementing authentication using WS-Federation in How do I configure PingAM as an Identity Provider for Microsoft Office 365 and Azure using WS-Federation? in the Knowledge Base.

Decide if you need custom authentication modules, server-side Scripts, or post-authentication plugins

If the default authentication modules and plugins do not suit your needs, consider coding your own.

Configure authentication modules

Before setting up your chains, configure the authentication modules you need.

Configure authentication chains

Use the chain designer to put together your chains quickly.

Configure post-authentication plugins

Post-authentication plugins allow you to manage session properties and run scripts after authentication, or after the user has logged out.

Configure authentication modules

The AM admin UI provides two places where you can configure authentication modules:

  • Under Configure > Authentication, you configure default properties for global authentication modules.

  • Under Realms > Realm Name > Authentication v Modules, you configure modules for your realm.

To configure the authentication modules required by your environment, refer to the following resources:

For module reference information, see Authentication modules configuration reference.

Create an authentication chain

After you have configured authentication modules and added the modules to the list of module instances, you can configure authentication chains. Authentication chains let you handle cases where alternate modules or credentials are needed. If you need modules in the chain to share user credentials, then set options for the module.

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

  2. On the Authentication Chains page, click Add Chain. Enter a chain name, and click Create.

    In the Edit Chain dialog, click Add a Module.

  3. Choose the authentication module in the drop-down list, and then assign appropriate criteria (Optional, Required, Requisite, Sufficient).

    Add as many modules as required.

  4. If you need modules in the chain to share user credentials, consider the following available options:

    Options to share credentials among modules
    iplanet-am-auth-store-shared-state-enabled

    Set iplanet-am-auth-store-shared-state-enabled=true to store the credentials captured by this module in shared state. This enables subsequent modules in the chain to access the credentials captured by this module. The shared state is cleared when the user successfully authenticates, quits the chain, or logs out.

    Default: true

    OATH and OTP codes are never added to the shared state, and cannot be shared between other modules in the chain.
    iplanet-am-auth-shared-state-enabled

    Set iplanet-am-auth-shared-state-enabled=true to allow this module to access the credentials, such as user name and password, that have been stored in shared state by previous modules in the authentication chain.

    Default: false

    iplanet-am-auth-shared-state-behavior-pattern

    Set iplanet-am-auth-shared-state-behavior-pattern=tryFirstPass to try authenticating with the username and password stored in shared state. If authentication fails, AM displays the login screen of this module for the user to re-enter their credentials.

    Set iplanet-am-auth-shared-state-behavior-pattern=useFirstPass to prevent the user from entering the username and password twice during authentication. Typically, you set the property to useFirstPass for all modules in the chain except the first module. If authentication fails, then the module fails.

    Default: tryFirstPass

    Enter the key and its value, and click Plus (+). When you finish entering the options, click OK.

    Examples

    For example, consider a chain with two modules sharing credentials according to the following settings: the first module in the chain has the option iplanet-am-auth-store-shared-state-enabled=true, and criteria REQUIRED.

    Configuring the first module in an authentication chain in a shared credentials configuration.
    Figure 2. Authentication chain first module

    The second module in the chain has options iplanet-am-auth-shared-state-enabled=true, iplanet-am-auth-shared-state-behavior-pattern=useFirstPass with criteria REQUIRED.

    Configuring the second module in an authentication chain in a shared credentials configuration.
    Figure 3. Authentication chain second module
  5. On the Settings tab, configure where AM redirects the user upon successful and failed authentication, and plug in your post-authentication processing classes as necessary.

    If you configure absolute URLs that are not in the same scheme, FQDN, and port as AM, you must also configure the Success and failure redirection URLs.

  6. Save your work.

    The following authentication sequence would occur: the user enters their credentials for the first module and successfully authenticates. The first module shares the credentials with the second module, successfully authenticating the user without prompting again for their credentials, unless the credentials for the first module do not successfully authenticate the user to the second module.

Login session timeouts for chains

Login pages have a session timeout that specifies the number of minutes before the session times out, if the user has not logged in. The login session timeout has two components:

  • The timeout of the specific authentication module.

    The default session timeout for an authentication module is two minutes.

  • The overall session timeout, set in Configure > Server Defaults > Session > Session Limits > Invalidate Session Max Time.

    The default overall session timeout is three minutes.

You must set the overall session timeout to a value greater than the complete authentication process (including any multi-page authentication processes). If you have chained authentication modules, with different timeouts, you must set the overall session timeout to a value greater than the sum of these timeouts.

Post-authentication plugins

Post-authentication plugins (PAP) let you include custom processing at the end of the authentication process and when users log out of AM.

In the AM admin UI, you add post-authentication plugins to an authentication chain. Go to Realms > Realm Name > Authentication > Chains > Auth Chain Name > Settings > Post Authentication Processing Class > Class Name.

See Create post-authentication plugins for chains for more information about post-authentication plugins.

Standard post-authentication plugins

AM provides some post-authentication plugins as part of the standard product delivery.

Class name: org.forgerock.openam.authentication.modules.adaptive.AdaptivePostAuthenticationPlugin

The adaptive authentication plugin serves to save cookies and profile attributes after successful authentication.

Add it to your authentication chains that use the adaptive authentication module configured to save cookies and profile attributes.

Class name: org.forgerock.openam.authentication.modules.common.JaspiAuthLoginModulePostAuthenticationPlugin

The Java Authentication Service Provider Interface (JASPI) post-authentication plugin initializes the underlying JASPI ServerAuth module.

JASPI defines a standard service provider interface (SPI) where developers can write message level authentication agents for Java containers on either the client side or the server side.

Class name: org.forgerock.openam.authentication.modules.oauth2.OAuth2PostAuthnPlugin

The OAuth 2.0 post-authentication plugin builds a global logout URL used by /oauth2c/OAuthLogout.jsp after successful OAuth 2.0 client authentication. This logs the resource owner out with the OAuth 2.0 provider when logging out of AM.

Before using this plugin, configure the OAuth 2.0 authentication module with the correct OAuth 2.0 Provider logout service URL, and set the Logout options to Log out or Prompt. This plugin cannot succeed unless those parameters are correctly set.

Sometimes OAuth 2.0 providers change their endpoints, including their logout URLs. When using a provider like Facebook, Google, or MSN, make sure you are aware when they change their endpoint locations so that you can change your client configuration accordingly.

Class name: org.forgerock.openam.authentication.modules.saml2.SAML2PostAuthenticationPlugin

The SAML v2.0 post-authentication plugin that gets activated for single logout. Supports HTTP-Redirect for logout-sending messages only.

Set the post-authentication processing class for the authentication chain that contains the SAML v2.0 authentication module.

Class name: org.forgerock.openam.authentication.modules.persistentcookie.PersistentCookieAuthModule

The Persistent Cookie Authentication Module provides logic for persistent cookie authentication in AM. It makes use of the JASPI JwtSession module to create and verify the persistent cookie.

Class name: com.sun.identity.authentication.spi.ReplayPasswd

Password replay post-authentication plugin class that uses a DES/ECB/NoPadding encryption algorithm. This class is deprecated in favor of the com.sun.identity.authentication.spi.JwtReplayPassword class. (Only one password replay post-authentication plugin class can be active for a given AM deployment.)

The plugin encrypts the password captured by AM during the authentication process and stores it in a session property. PingGateway or a web agent looks up the property, decrypts it, and replays the password into legacy applications.

To configure password replay for AM and PingGateway, refer to Password replay from AM in the PingGateway documentation.

Class name: com.sun.identity.authentication.spi.JwtReplayPassword

Password replay post-authentication plugin class that uses a JWT-based AES A128CBC-HS256 encryption algorithm. (Only one password replay post-authentication plugin class can be active for a given AM deployment.)

The plugin encrypts the password captured by AM during the authentication process and stores it in a session property. PingGateway looks up the property, decrypts it, and replays the password into legacy applications.

Only version 6 or later is supported.

To configure password replay for AM and PingGateway, refer to Password replay from AM in the PingGateway documentation.

If necessary, you can also write your own custom post-authentication plugin as described in Create post-authentication plugins for chains.

Customize authentication chains

Your deployment might require customizing standard authentication chain features.

Create a custom authentication module

This section shows how to customize authentication with a sample custom authentication module. For deployments with particular requirements not met by existing AM authentication modules, determine whether you can adapt one of the built-in or extension modules for your needs. If not, build the functionality into a custom authentication module.

The sample custom authentication module

The sample custom authentication module prompts for a user name and password to authenticate the user, and handles error conditions. The sample shows how you integrate an authentication module into AM such that you can configure the module through the AM admin UI, and also localize the user interface.

Learn about downloading and building PingAM sample source code in the following Knowledge Base article: How do I access and build the sample code provided for PingAM?.

Get a local clone so that you can try the sample on your system. In the sources, you find the following files under the /path/to/openam-samples-external/custom-authentication-module directory:

pom.xml

Apache Maven project file for the module

This file specifies how to build the sample authentication module, and also specifies its dependencies on AM components and on the Java Servlet API.

src/main/java/org/forgerock/openam/examples/SampleAuth.java

Core class for the sample authentication module

This class is called by AM to initialize the module and to process authentication. See Sample authentication logic for details.

src/main/java/org/forgerock/openam/examples/SampleAuthPrincipal.java

Class implementing java.security.Principal interface that defines how to map credentials to identities

This class is used to process authentication. See The Sample Auth Principal for details.

src/main/resources/amAuthSampleAuth.properties

Properties file mapping UI strings to property values

This file makes it easier to localize the UI. See Sample Auth properties for details.

src/main/resources/amAuthSampleAuth.xml

Configuration file for the sample authentication service

This file is used when registering the authentication module with AM. See The Sample Auth Service Configuration for details.

src/main/resources/config/auth/default/SampleAuth.xml

Callback file for deprecated AM classic UI authentication pages

The sample authentication module does not include localized versions of this file. See Sample Auth callbacks for details.

src/main/java/org/forgerock/openam/examples/SampleAuthPlugin.java

These files serve to register the plugin with AM.

The Java class, SampleAuthPlugin, implements the org.forgerock.openam.plugins.AmPlugin interface. In the sample, this class registers the SampleAuth implementation, and the amAuthSampleAuth service schema for configuration.

The services file, org.forgerock.openam.plugins.AmPlugin, holds the fully qualified class name of the AmPlugin that registers the custom implementations. In this case, org.forgerock.openam.examples.SampleAuthPlugin.

For an explanation of service loading, see the ServiceLoader Javadoc.

Sample Auth properties

AM uses a Java properties file per locale to retrieve the appropriate, localized strings for the authentication module.

The following is the Sample Authentication Module properties file, amAuthSampleAuth.properties.

#
# The contents of this file are subject to the terms of the Common Development and
# Distribution License (the License). You may not use this file except in compliance with the
# License.
#
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
# specific language governing permission and limitations under the License.
#
# When distributing Covered Software, include this CDDL Header Notice in each file and include
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
# Header, with the fields enclosed by brackets [] replaced by your own identifying
# information: "Portions copyright [year] [name of copyright owner]".
#
# Copyright 2011-2017 ForgeRock AS. All Rights Reserved
#

sampleauth-service-description=Sample Authentication Module
a500=Authentication Level
a501=Service Specific Attribute

sampleauth-ui-login-header=Login
sampleauth-ui-username-prompt=User Name:
sampleauth-ui-password-prompt=Password:

sampleauth-error-1=Error 1 occurred during the authentication
sampleauth-error-2=Error 2 occurred during the authentication

Sample Auth callbacks

AM callbacks XML files are used to build the deprecated classic UI to prompt the user for identity information needed to process the authentication. The document type for a callback XML file is described in WEB-INF/Auth_Module_Properties.dtd where AM is deployed.

The value of the moduleName property in the callbacks file must match your custom authentication module’s class name. Observe that the module name SampleAuth, shown in the example below, matches the class name in SampleAuth.java.

<?xml version="1.0" encoding="UTF-8"?>
<!--
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2011-2017 ForgeRock AS. All Rights Reserved
 *
-->
<!DOCTYPE ModuleProperties PUBLIC
 "=//iPlanet//Authentication Module Properties XML Interface 1.0 DTD//EN"
        "jar://com/sun/identity/authentication/Auth_Module_Properties.dtd">

<ModuleProperties moduleName="SampleAuth" version="1.0" >
    <Callbacks length="0" order="1" timeout="600" header="#NOT SHOWN#" />
    <Callbacks length="2" order="2" timeout="600" header="#TO BE SUBSTITUTED#">
        <NameCallback isRequired="true">
            <Prompt>#USERNAME#</Prompt>
        </NameCallback>
        <PasswordCallback echoPassword="false" >
            <Prompt>#PASSWORD#</Prompt>
        </PasswordCallback>
    </Callbacks>
    <Callbacks length="1" order="3" timeout="600" header="#TO BE SUBSTITUTED#"
        error="true" >
        <NameCallback>
            <Prompt>#THE DUMMY WILL NEVER BE SHOWN#</Prompt>
        </NameCallback>
    </Callbacks>
</ModuleProperties>

This file specifies three states.

  1. The initial state (order="1") is used dynamically to replace the dummy strings shown between hashes (for example, #USERNAME#) by the substituteUIStrings() method in SampleAuth.java.

  2. The next state (order="2") handles prompting the user for authentication information.

  3. The last state (order="3") has the attribute error="true". If the authentication module state machine reaches this order then the authentication has failed. The NameCallback is not used and not displayed to user. AM requires that the callbacks array have at least one element. Otherwise AM does not permit header substitution.

Sample authentication logic

An AM authentication module must extend the com.sun.identity.authentication.spi.AMLoginModule abstract class, and must implement the methods shown below.

The account lockout functionality in AM is triggered by counting invalid password exceptions, rather than invalid login exceptions.

To trigger account lockouts after repeated failed attempts, ensure your modules throw InvalidPasswordException exceptions instead of AuthLoginException exceptions when appropriate, as per the code below.

See the PingAM Java SDK API Specification for reference.

public void init(Subject subject, Map sharedState, Map options)

// OpenAM calls the process() method when the user submits authentication
// information. The process() method determines what happens next:
// success, failure, or the next state specified by the order
// attribute in the callbacks XML file.
public int process(Callback[] callbacks, int state) throws LoginException

// OpenAM expects the getPrincipal() method to return an implementation of
// the java.security.Principal interface.
public Principal getPrincipal()

AM does not reuse authentication module instances. This means that you can store information specific to the authentication process in the instance.

The implementation, SampleAuth.java, is shown below:

/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2011-2023 ForgeRock AS. All Rights Reserved
 */

package org.forgerock.openam.examples;

import java.security.Principal;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.identity.authentication.spi.AMLoginModule;
import com.sun.identity.authentication.spi.AuthLoginException;
import com.sun.identity.authentication.spi.InvalidPasswordException;
import com.sun.identity.authentication.util.ISAuthConstants;
import com.sun.identity.shared.datastruct.CollectionHelper;

/**
 * SampleAuth authentication module example.
 *
 * If you create your own module based on this example, you must modify all
 * occurrences of "SampleAuth" in addition to changing the name of the class.
 *
 * Please refer to OpenAM documentation for further information.
 *
 * Feel free to look at the code for authentication modules delivered with
 * OpenAM, as they implement this same API.
 */
public class SampleAuth extends AMLoginModule {

    // Name for the debug-log
    private final static String DEBUG_NAME = "SampleAuth";
    private final static Logger debug = LoggerFactory.getLogger(SampleAuth.class);

    // Name of the resource bundle
    private final static String amAuthSampleAuth = "amAuthSampleAuth";

    // User names for authentication logic
    private final static String USERNAME = "demo";
    private final static String PASSWORD = "Ch4ng31t";

    private final static String ERROR_1_USERNAME = "test1";
    private final static String ERROR_2_USERNAME = "test2";

    // Orders defined in the callbacks file
    private final static int STATE_BEGIN = 1;
    private final static int STATE_AUTH = 2;
    private final static int STATE_ERROR = 3;

    // Errors properties
    private final static String SAMPLE_AUTH_ERROR_1 = "sampleauth-error-1";
    private final static String SAMPLE_AUTH_ERROR_2 = "sampleauth-error-2";

    private Map<String, Set<String>> options;
    private ResourceBundle bundle;
    private Map<String, String> sharedState;

    public SampleAuth() {
        super();
    }


    /**
     * This method stores service attributes and localized properties for later
     * use.
     * @param subject
     * @param sharedState
     * @param options
     */
    @Override
    public void init(Subject subject, Map sharedState, Map options) {

        debug.debug("SampleAuth::init");

        this.options = options;
        this.sharedState = sharedState;
        this.bundle = amCache.getResBundle(amAuthSampleAuth, getLoginLocale());
    }

    @Override
    public int process(Callback[] callbacks, int state) throws LoginException {

        debug.debug("SampleAuth::process state: {}", state);

        switch (state) {

            case STATE_BEGIN:
                // Intialize Callback list if used in chain with
                // iplanet-am-auth-shared-state-enabled=true
                setForceCallbacksRead(true);
                forceCallbacksInit();

                // No time wasted here - simply modify the UI and
                // proceed to next state
                substituteUIStrings();
                return STATE_AUTH;

            case STATE_AUTH:
                // Get data from callbacks. Refer to callbacks XML file.
                NameCallback nc = (NameCallback) callbacks[0];
                PasswordCallback pc = (PasswordCallback) callbacks[1];
                String username = nc.getName();
                String password = String.valueOf(pc.getPassword());

                //First errorstring is stored in "sampleauth-error-1" property.
                if (ERROR_1_USERNAME.equals(username)) {
                    setErrorText(SAMPLE_AUTH_ERROR_1);
                    return STATE_ERROR;
                }

                //Second errorstring is stored in "sampleauth-error-2" property.
                if (ERROR_2_USERNAME.equals(username)) {
                    setErrorText(SAMPLE_AUTH_ERROR_2);
                    return STATE_ERROR;
                }

                if (USERNAME.equals(username) && PASSWORD.equals(password)) {
                    debug.debug("SampleAuth::process User '{}' " +
                            "authenticated with success.", username);
                    return ISAuthConstants.LOGIN_SUCCEED;
                }

                throw new InvalidPasswordException("password is wrong",
                        USERNAME, isReturningPrincipalAsDn());

            case STATE_ERROR:
                return STATE_ERROR;
            default:
                throw new AuthLoginException("invalid state");
        }
    }

    @Override
    public Principal getPrincipal() {
        return new SampleAuthPrincipal(USERNAME);
    }

    private void setErrorText(String err) throws AuthLoginException {
        // Receive correct string from properties and substitute the
        // header in callbacks order 3.
        substituteHeader(STATE_ERROR, bundle.getString(err));
    }

    private void substituteUIStrings() throws AuthLoginException {
        // Get service specific attribute configured in OpenAM
        String ssa = CollectionHelper.getMapAttr(options, "specificAttribute");

        // Get property from bundle
        String new_hdr = ssa + " " +
                bundle.getString("sampleauth-ui-login-header");
        substituteHeader(STATE_AUTH, new_hdr);

        replaceCallback(STATE_AUTH, 0, new NameCallback(
                bundle.getString("sampleauth-ui-username-prompt")));
        replaceCallback(STATE_AUTH, 1, new PasswordCallback(
                bundle.getString("sampleauth-ui-password-prompt"), false));
    }
}

The Sample Auth Principal

The implementation, SampleAuthPrincipal.java, is shown below:

/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2011-2017 ForgeRock AS. All Rights Reserved
 */

package org.forgerock.openam.examples;

import java.io.Serializable;
import java.security.Principal;

/**
 * SampleAuthPrincipal represents the user entity.
 */
public class SampleAuthPrincipal implements Principal, Serializable {
    private final static String COLON = " : ";

    private final String name;

    public SampleAuthPrincipal(String name) {

        if (name == null) {
            throw new NullPointerException("illegal null input");
        }

        this.name = name;
    }

    /**
     * Return the LDAP username for this <code>SampleAuthPrincipal</code>.
     *
     * @return the LDAP username for this <code>SampleAuthPrincipal</code>
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * Return a string representation of this <code>SampleAuthPrincipal</code>.
     *
     * @return a string representation of this
     *         <code>TestAuthModulePrincipal</code>.
     */
    @Override
    public String toString() {
        return new StringBuilder().append(this.getClass().getName())
                .append(COLON).append(name).toString();
    }

    /**
     * Compares the specified Object with this <code>SampleAuthPrincipal</code>
     * for equality. Returns true if the given object is also a
     * <code> SampleAuthPrincipal </code> and the two SampleAuthPrincipal have
     * the same username.
     *
     * @param o Object to be compared for equality with this
     *          <code>SampleAuthPrincipal</code>.
     * @return true if the specified Object is equal equal to this
     *         <code>SampleAuthPrincipal</code>.
     */
    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }

        if (this == o) {
            return true;
        }

        if (!(o instanceof SampleAuthPrincipal)) {
            return false;
        }
        SampleAuthPrincipal that = (SampleAuthPrincipal) o;

        if (this.getName().equals(that.getName())) {
            return true;
        }
        return false;
    }

    /**
     * Return a hash code for this <code>SampleAuthPrincipal</code>.
     *
     * @return a hash code for this <code>SampleAuthPrincipal</code>.
     */
    @Override
    public int hashCode() {
        return name.hashCode();
    }
}

The Sample Auth Service Configuration

AM requires that all authentication modules be configured by means of an AM service. At minimum, the service must include an authentication level attribute. Your module can access these configuration attributes in the options parameter passed to the init() method.

Some observations about the service configuration file follow in the list below.

  • The document type for a service configuration file is described in WEB-INF/sms.dtd where AM is deployed.

  • The service name is derived from the module name. The service name must have the following format:

    • It must start with either iPlanetAMAuth or sunAMAuth.

    • The module name must follow. The case of the module name must match the case of the class that implements the custom authentication module.

    • It must end with Service.

    In the Sample Auth service configuration, the module name is SampleAuth and the service name is iPlanetAMAuthSampleAuthService.

  • The service must have a localized description, retrieved from a properties file.

  • The i18nFileName attribute in the service configuration holds the default (non-localized) base name of the Java properties file. The i18nKey attributes indicate properties keys to string values in the Java properties file.

  • The authentication level attribute name must have the following format:

    • It must start with iplanet-am-auth-, sun-am-auth-, or forgerock-am-auth-.

    • The module name must follow, and must appear in lower case if the attribute name starts with iplanet-am-auth- or forgerock-am-auth-. If the attribute name starts with sun-am-auth-, it must exactly match the case of the module name as it appears in the service name.

    • It must end with -auth-level.

    In the Sample Auth service configuration, the authentication level attribute name is iplanet-am-auth-sampleauth-auth-level.

  • The Sample Auth service configuration includes an example sampleauth-service-specific-attribute, which can be configured through the AM admin UI.

The service configuration file, amAuthSampleAuth.xml, is shown below. Save a local copy of this file, which you use when registering the module.

<?xml version="1.0" encoding="UTF-8"?>
<!--
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2011-2020 ForgeRock AS. All Rights Reserved
 *
-->
<!DOCTYPE ServicesConfiguration
    PUBLIC "=//iPlanet//Service Management Services (SMS) 1.0 DTD//EN"
    "jar://com/sun/identity/sm/sms.dtd">

<ServicesConfiguration>
 <Service name="iPlanetAMAuthSampleAuthService" version="1.0">
  <Schema
   serviceHierarchy="/DSAMEConfig/authentication/iPlanetAMAuthSampleAuthService"
   i18nFileName="amAuthSampleAuth" revisionNumber="10"
   i18nKey="sampleauth-service-description" resourceName="sample">
   <Organization>
    <!-- Specify resourceName for a JSON-friendly property in the REST SMS -->
    <AttributeSchema name="iplanet-am-auth-sampleauth-auth-level" resourceName="authLevel"
     type="single" syntax="number_range" rangeStart="0" rangeEnd="2147483647"
     i18nKey="a500">
     <DefaultValues>
      <Value>1</Value>
     </DefaultValues>
    </AttributeSchema>

    <!-- No need for resourceName when the name is JSON-compatible -->
    <AttributeSchema name="specificAttribute"
     type="single" syntax="string" validator="no" i18nKey="a501" />

    <!--
     For Auth Modules, the parent Schema element specifies the REST SMS resourceName,
     and the nested SubSchema must have resourceName="USE-PARENT"
    -->
    <SubSchema name="serverconfig" inheritance="multiple" resourceName="USE-PARENT">
     <AttributeSchema name="iplanet-am-auth-sampleauth-auth-level" resourceName="authLevel"
      type="single" syntax="number_range" rangeStart="0" rangeEnd="2147483647"
      i18nKey="a500">
      <DefaultValues>
       <Value>1</Value>
      </DefaultValues>
     </AttributeSchema>

     <!-- No need for a DefaultValues element when the default is blank -->
     <AttributeSchema name="specificAttribute"
      type="single" syntax="string" validator="no" i18nKey="a501" />

    </SubSchema>
   </Organization>
  </Schema>
 </Service>
</ServicesConfiguration>

Building and Installing the Sample Custom Auth Module

Build the module with Apache Maven, and install the module in AM.

Learn about downloading and building PingAM sample source code in the following Knowledge Base article: How do I access and build the sample code provided for PingAM?.

Installing the Module

Installing the sample authentication module consists of copying the .jar file to AM’s WEB-INF/lib/ directory, registering the module with AM, and then restarting AM or the web application container where it runs.

  1. Copy the sample authentication module .jar file to WEB-INF/lib/ where AM is deployed.

    $ cp target/custom*.jar /path/to/tomcat/webapps/openam/WEB-INF/lib/
  2. Restart AM or the container in which it runs.

    For example if you deployed AM in Apache Tomcat, then you shut down Tomcat and start it again.

    $ /path/to/tomcat/bin/shutdown.sh
    $ /path/to/tomcat/bin/startup.sh
    $ tail -1 /path/to/tomcat/logs/catalina.out
    INFO: Server startup in 14736 ms

Configure and test the Sample Auth module

Authentication modules are registered as services with AM globally, and then set up for use in a particular realm. In this example, you set up the sample authentication module for use in the realm / (Top Level Realm).

In the AM admin UI, go to Realms > Top Level Realm > Authentication > Modules. Click Add Module to create an instance of the Sample Authentication Module. Name the module Sample.

The module type is Sample Authentication Module.

Click Create, and then configure the authentication module as appropriate.

The module has two configurable properties, Authentication Level and Service Specific Attribute.

Now that the module is configured, log out of the AM admin UI.

Finally, try the module by specifying the Sample module. Browse to the login URL such as https://openam.example.com:8443/openam/XUI/?realm=/&module=Sample#login, and then authenticate with user name demo and password Ch4ng31t.

On the login page, you can see the string you set as the value of Service Specific Attribute.

After authentication you are redirected to the end user page for the demo user. You can logout of the AM admin UI, and then try to authenticate as the non-existent user test123 to see what the error handling looks like to the user.

Server-side authentication scripts in authentication modules

This section demonstrates how to use the default server-side authentication script. An authentication script can be called from a Scripted authentication module.

The default server-side authentication script only authenticates a subject when the current time on the AM server is between 09:00 and 17:00. The script also uses the logger and httpClient functionality provided in the scripting API.

To examine the contents of the default server-side authentication script in the AM admin UI, go to Realms > Top Level Realm > Scripts, and click Scripted Module - Server Side.

For general information about scripting in AM, see Scripting.

Prepare AM to use server-side authentication scripts

AM requires a small amount of configuration before trying the example server-side authentication script. You must create an authentication module of the Scripted type, and then include it in an authentication chain, which can then be used when logging in to AM. You must also ensure the demo user has an associated postal address.

Create a scripted authentication module that uses the default server-side authentication acript

In this procedure, create a Scripted Authentication module, and link it to the default server-side authentication script.

  1. Log in as an AM administrator, for example amAdmin.

  2. Go to Realms > Top Level Realm > Authentication > Modules.

  3. On the Authentication Modules page, click Add Module.

  4. On the New Module page, enter a module name, such as myScriptedAuthModule, from the Type drop-down list, select Scripted Module, and click Create.

  5. On the module configuration page:

    • Uncheck the Client-side Script Enabled checkbox.

    • From the Server-side Script drop-down list, select Scripted Module - Server Side.

    • Click Save Changes.

Create an authentication chain that uses a Scripted Authentication module

In this procedure, create an authentication chain that uses a Data Store authentication module and the Scripted authentication module created in the previous procedure.

  1. Log in as an AM administrator, for example amAdmin.

  2. Go to Realms > Top Level Realm > Authentication > Chains.

  3. On the Authentication Chains page, click Add Chain.

  4. On the Add Chain page, enter a name, such as myScriptedChain, and click Create.

  5. On the Edit Chain tab, click Add a Module.

  6. In the New Module dialog box:

    • From the Select Module drop-down list, select DataStore.

    • From the Select Criteria drop-down list, select Required.

    • Click OK.

      The Data Store authentication module checks the user credentials, whereas the Scripted authentication module does not check credentials, but instead only checks that the authentication request is processed during working hours. Without the Data Store module, the username in the Scripted authentication module cannot be determined. Therefore, do not configure the Scripted authentication module (server-side script) as the first module in an authentication chain, because it needs a username.

  7. On the Edit Chain tab, click Add Module.

  8. In the New Module dialog box:

    • From the Select Module drop-down list, select the Scripted Module from the previous procedure, for example myScriptedAuthModule.

    • From the Select Criteria drop-down list, select Required.

    • Click OK.

      The resulting chain resembles the following:

      Two modules are required: DataStore and Scripted Module - Server Side.
  9. On the Edit Chain tab, click Save Changes.

Add a postal address to the demo user
  1. Log in as an AM administrator, for example amAdmin.

  2. Go to Realms > Top Level Realm > Identities.

  3. On the Identities tab, click the demo user.

  4. In the Home Address field, enter a valid postal address, with lines separated by commas.

    For example:

    ForgeRock Inc., 201 Mission St #2900, San Francisco, CA 94105, USA
  5. Save your changes.

Trying the default server-side authentication script

This section shows how to log in using an authentication chain that contains a Scripted authentication module, which in turn uses the default server-side authentication script.

The default server-side authentication script gets the postal address of a user after they authenticate using a Data Store authentication module, and then makes an HTTP call to an external web service to determine the longitude and latitude of the address. Using these details, a second HTTP call is performed to get the local time at those coordinates. If that time is between two preset limits, authentication is allowed, and the user is given a session and redirected to the profile page.

Log in using a chain containing a Scripted Authentication module
  1. Log out of AM.

  2. In a browser, go to the AM login URL, and specify the authentication chain created in the previous procedure as the value of the service parameter.

    For example:

    https://openam.example.com:8443/openam/XUI/?service=myScriptedChain#login
  3. Log in as user demo with password Ch4ng31t.

    If login is successful, the user profile page is displayed. The script will also output messages, such as the following in the debug/Authentication log file:

    Starting scripted authentication
    amScript:02/27/2017 03:22:42:881 PM GMT: Thread[ScriptEvaluator-5,5,main]: TransactionId[7635cd7c-ea97-4be6-8694-9e2be8642d56-8581]
    User: demo
    amScript:02/27/2017 03:22:42:882 PM GMT: Thread[ScriptEvaluator-5,5,main]: TransactionId[7635cd7c-ea97-4be6-8694-9e2be8642d56-8581]
    User address: ForgeRock Inc., 201 Mission St #2900, San Francisco, CA 94105, USA
    amScript:02/27/2017 03:22:42:929 PM GMT: Thread[ScriptEvaluator-5,5,main]: TransactionId[7635cd7c-ea97-4be6-8694-9e2be8642d56-8581]
    User REST Call. Status: [Status: 200 OK]
    amScript:02/27/2017 03:27:31:646 PM GMT: Thread[ScriptEvaluator-7,5,main]: TransactionId[7635cd7c-ea97-4be6-8694-9e2be8642d56-8581]
    latitude:37.7914374 longitude:-122.3950694
    amScript:02/27/2017 03:27:31:676 PM GMT: Thread[ScriptEvaluator-7,5,main]: TransactionId[7635cd7c-ea97-4be6-8694-9e2be8642d56-8581]
    User REST Call. Status: [Status: 200 OK]
    amScript:02/27/2017 03:27:31:676 PM GMT: Thread[ScriptEvaluator-7,5,main]: TransactionId[7635cd7c-ea97-4be6-8694-9e2be8642d56-8581]
    Current time at the users location: 10
    amScript:02/27/2017 03:27:31:676 PM GMT: Thread[ScriptEvaluator-7,5,main]: TransactionId[7635cd7c-ea97-4be6-8694-9e2be8642d56-8581]
    Authentication allowed!
    amLoginModule:02/27/2017 03:27:31:676 PM GMT: Thread[http-nio-8080-exec-4,5,main]: TransactionId[7635cd7c-ea97-4be6-8694-9e2be8642d56-8581]
    Login NEXT State : -1
    amLoginModule:02/27/2017 03:27:31:676 PM GMT: Thread[http-nio-8080-exec-4,5,main]: TransactionId[7635cd7c-ea97-4be6-8694-9e2be8642d56-8581]
    SETTING Module name.... :myScriptedAuthModule
    amAuth:02/27/2017 03:27:31:676 PM GMT: Thread[http-nio-8080-exec-4,5,main]: TransactionId[7635cd7c-ea97-4be6-8694-9e2be8642d56-8581]
    Module name is .. myScriptedAuthModule
    amAuth:02/27/2017 03:27:31:676 PM GMT: Thread[http-nio-8080-exec-4,5,main]: TransactionId[7635cd7c-ea97-4be6-8694-9e2be8642d56-8581]
    successModuleSet is : [DataStore, myScriptedAuthModule]
    amJAAS:02/27/2017 03:27:31:676 PM GMT: Thread[http-nio-8080-exec-4,5,main]: TransactionId[7635cd7c-ea97-4be6-8694-9e2be8642d56-8581]
    login success

    The default server-side authentication script outputs log messages at the message and error level.

    AM does not log debug messages from scripts by default. You can configure AM to log such messages by setting the debug log level for the amScript service. For details, see Debug logging.

  4. To test that the script is being used as part of the login process, edit the script to alter the times when authentication is allowed:

    • Log out the demo user.

    • Log in as an AM administrator, for example amAdmin.

    • Go to Realms > Top Level Realm > Scripts > Scripted Module - Server Side.

    • In the script, swap the values for START_TIME and END_TIME, for example:

      var START_TIME = 17;
      var END_TIME   = 9;
    • Click Save.

    • Repeat steps 1, 2, and 3 above, logging into the module as the demo user as before. The authentication result will be the opposite of the previous result, as the allowed times have inverted.

Create post-authentication plugins for chains

Post-authentication plugins (PAP) let you include custom processing at the following places in the authentication cycle:

  • At the end of the authentication process, immediately before a user is authenticated.

  • When a user logs out of an AM session.

A common use of post-authentication plugins is to set state information in the session object in conjunction with web or Java agents. The post-authentication plugin sets custom session properties, and then the web or Java agent injects the custom properties into the header sent to the protected application.

Two issues should be considered when writing a post-authentication plugin for an AM deployment that uses client-side sessions:

Cookie size

You can set an unlimited number of session properties in a post-authentication plugin. When AM creates a client-side session, it writes the session properties into the session cookie, increasing the size of the cookie. Very large session cookies can exceed browser limitations. Therefore, when implementing a post-authentication plugin in a deployment with client-side sessions, be sure to monitor the session cookie size and verify that you have not exceeded browser cookie size limits.

For more information about client-side session cookies, see Session cookies and session security.

Cookie security

The AM administrator secures custom session properties in sessions residing in the CTS token store by using firewalls and other typical security techniques.

However, when using client-side sessions, custom session properties are written in cookies and reside on end users' systems. Cookies can be long-lasting and might represent a security issue if any session properties are of a sensitive nature. When developing a post-authentication plugin for a deployment that uses client-side sessions, be sure that you are aware of the measures securing the session contained within the cookie.

For more information about client-side session cookie security, see Configure client-side session security.

Design your post-authentication plugin

Your post-authentication plugin class implements the AMPostAuthProcessInterface interface, and in particular the following three methods.

public void onLoginSuccess(
  Map requestParamsMap,
  HttpServletRequest request,
  HttpServletResponse response,
  SSOToken token
) throws AuthenticationException

public void onLoginFailure(
  Map requestParamsMap,
  HttpServletRequest request,
  HttpServletResponse response
) throws AuthenticationException

public void onLogout(
  HttpServletRequest request,
  HttpServletResponse response,
  SSOToken token
) throws AuthenticationException

AM calls the onLoginSuccess() and onLoginFailure() methods immediately before informing the user of login success or failure, respectively. AM calls the onLogout() method only when the user actively logs out, not when a user’s session times out.

See the PingAM Java SDK API Specification for reference.

These methods can perform whatever processing you require. Yet, know that AM calls your methods synchronously as part of the authentication process. Therefore, if your methods take a long time to complete, you will keep users waiting. Minimize the processing done in your post-authentication methods.

Implementing a post-authentication plugin in the top level realm can have unexpected effects. At the top level realm, AM invokes the post-authentication plugin for all types of authentication during startup, including user logins and internal administrative logins. The best practice is to let end users into subrealms only, and administrators into the top level realm. If you must execute the post-authentication plugin for administrative logins, make sure that the plugin can also handle internal authentications.

An alternate solution is to configure the post-authentication plugin on a per authentication chain basis, which can be configured separately for user logins or internal administrative logins.

Realm-level post-authentication plugins are only called when no post-authentication plugin is configured for the authentication chain.

Post-authentication plugins must be stateless: they do not maintain state between login and logout. Store any information that you want to save between login and logout in a session property. AM stores session properties in the CTS token store after login, and retrieves them from the token store as part of the logout process.

Build your sample post-authentication plugin

The following example post-authentication plugin sets a session property during successful login, writing to its debug log if the operation fails.

/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2011-2019 ForgeRock AS. All Rights Reserved
 */

package com.forgerock.openam.examples;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.sun.identity.authentication.spi.AMPostAuthProcessInterface;
import com.sun.identity.authentication.spi.AuthenticationException;

/**
 * Set a session property on successful authentication.
 * If authentication fails, log a debug message.
 */
public class SamplePAP implements AMPostAuthProcessInterface {
    private final static String PROP_NAME = "MyProperty";
    private final static String PROP_VALUE = "MyValue";
    private final static String DEBUG_FILE = "SamplePAP";

    private Logger debug = LoggerFactory.getLogger(SamplePAP.class);

    public void onLoginSuccess(
            Map requestParamsMap,
            HttpServletRequest request,
            HttpServletResponse response,
            SSOToken token
    ) throws AuthenticationException {
        try {
            token.setProperty(PROP_NAME, PROP_VALUE);
        } catch (SSOException e) {
            debug.error("Unable to set property");
        }
    }

    public void onLoginFailure(
            Map requestParamsMap,
            HttpServletRequest request,
            HttpServletResponse response
    ) throws AuthenticationException {
        // Not used
    }

    public void onLogout(
            HttpServletRequest request,
            HttpServletResponse response,
            SSOToken token
    ) throws AuthenticationException {
        // Not used
    }
}

If you have not already done so, download and build the sample code.

Learn about downloading and building PingAM sample source code in the following Knowledge Base article: How do I access and build the sample code provided for PingAM?.

In the sources, you find the following files:

pom.xml

Apache Maven project file for the module

This file specifies how to build the sample post-authentication plugin, and also specifies its dependencies on AM components and on the Servlet API.

src/main/java/com/forgerock/openam/examples/SamplePAP.java

Core class for the sample post-authentication plugin

Once built, copy the .jar to the WEB-INF/lib directory where you deployed AM.

$ cp target/*.jar /path/to/tomcat/webapps/openam/WEB-INF/lib/

Restart AM or the container in which it runs.

Configure your post-authentication plugin

You can associate post-authentication plugins with realms or services (authentication chains). Where you configure the plugin depends on the scope to which the plugin should apply:

  • Plugins configured at the realm level are executed when authenticating to any authentication chain in the realm, provided the authentication chain does not have an associated plugin.

  • Plugins configured at the service level are executed if that authentication chain is used for authentication. Any plugins configured at the realm level will not execute.

In the AM admin UI, go to Realms > Realm Name > Authentication > Settings > Post Authentication Processing. In the Authentication Post Processing Classes list, add the sample plugin class, com.forgerock.openam.examples.SamplePAP, and click Save.

Alternatively, you can configure sample plugin for the realm by using the ssoadm command.

$ ssoadm set-svc-attrs \
 --adminid uid=amAdmin,ou=People,dc=openam,dc=forgerock,dc=org \
 --password-file /tmp/pwd.txt \
 --servicename iPlanetAMAuthService \
 --realm /myRealm \
 --attributevalues iplanet-am-auth-post-login-process-class=
 com.forgerock.openam.examples.SamplePAP
iPlanetAMAuthService under /myRealm was
 modified.

Test your post-authentication plugin

To test the sample post-authentication plugin, login successfully to AM in the scope where the plugin is configured. For example, if you configured your plugin for the realm, /myRealm, specify the realm in the login URL.

https://openam.example.com:8443/openam/XUI/?realm=/myRealm#login

Although you will not notice anywhere in the user interface that AM calls your plugin, a web or Java agent or custom client code could retrieve the session property that your plugin added to the user session.

Copyright © 2010-2024 ForgeRock, all rights reserved.