Simple Authentication Action

Simple Authentication Action

tutorials

Authentication Actions present a tool for orchestrating what happens after authentication but before the session is committed. In other words, the user has authenticated, but the system hasn’t yet produced an authenticated session, which later can be used for Single Sign-On etc.

There are several different Authentication Actions available in the Curity Identity Server out-of-the-box. The actions on their own can handle many different things such as

  • Attribute transformations
  • Attribute lookup from various data sources
  • Approve or deny authentication based on a given condition
  • Handle linking of accounts in various ways
  • Prompt for attribute information
  • Trigger password reset
  • Initiate separate flow for Multi-Factor Authentication or other flows to approve the login

Any Authenticator can be configured with any number of actions to run after login, or after SSO. They are executed in the configured order so they can be chained and depend on each other. A condition Action can be used to determine if an action should be invoked or not. It is also possible to create a sequence of Actions that can be re-used across multiple Authenticators.

Prerequisites

Maven Archetype

Maven Archetypes are essentially template projects. Curity Maven Archetypes are available for different Curity plugin types to quickly generate a skeleton of a given plugin.

After the Archetypes are installed the following can be used to generate an Authentication Action Plugin that can be used as a basis for a specific implementation.

mvn -B archetype:generate \
    -DarchetypeArtifactId=identityserver.plugins.archetypes.authentication-action \
    -DarchetypeGroupId=io.curity \
    -DarchetypeVersion=1.3.1 \
    -DartifactId=identityserver.plugins.authenticationactions.date_time \
    -DgroupId=io.curity.identityserver.plugin \
    -DpluginName=TimeDeny \
    -Dversion=1.0.0-SNAPSHOT

The generated plugin artifacts will now be available in a folder named identityserver.plugins.authenticationactions.date_time (the artifactId). The Class that implements the actual Action also includes a very simple apply method that will be covered in a later section in this article.

Descriptor

A Plugin Descriptor informs the Curity Identity Server of the characteristics of the plugin itself. Curity scans for implementations of the subtypes of PluginDescriptor when looking for plugins. In this article the example plugin will be an Authentication Action Plugin and thus the subtype will be AuthenticationActionPluginDescriptor.

public final class TimeDenyAuthenticationActionPluginDescriptor implements AuthenticationActionPluginDescriptor<TimeDenyAuthenticationActionConfiguration>

The Authentication Action Descriptor’s getPluginImplementationType method returns the plugin implementation type. This must be unique across all of the plugins that are deployed to the Curity Identity Server.

@Override
public String getPluginImplementationType()
{
    return "time-deny";
}

All plugins must provide an interface that extends Configuration representing its runtime configuration. This is done via the PluginDescriptor.

@Override
public Class<? extends TimeDenyAuthenticationActionConfiguration> getConfigurationType()
{
    return TimeDenyAuthenticationActionConfiguration.class;
}

Configuration

The configuration Interface extends the Configuration class and is fairly simple. Members of data type boolean, int, long and double can be declared.

The name of the configuration option in the UI will correspond to the name of the method with is or get removed. The example below would correspond to a UI element named Enabled. Since its a Boolean datatype will be an on/off switch type UI element.

Boolean isEnabled();

It is possible to define a member as an Interface. Here the configuration refers to the TimeConfiguration Interface

TimeConfiguration getNoAccessBefore();

The TimeConfiguration interface in its turn can implement multiple configuration members.

@Description("Hour at which access should be allowed or denied")
@DefaultInteger(0)
@RangeConstraint(min = 0, max = 23)
int getHour();

@Description("Minute at which access should be allowed or denied")
@DefaultInteger(0)
@RangeConstraint(min = 0, max = 59)
int getMinute();

Configurations can be made optional using Optional<T> where T is any other acceptable type.

Annotations

Several annotations can be used to further describe the configuration values. As exemplified above by the use of @Description, @DefaultInteger and @RangeConstraint. It is also possible to override the name of the configuration name in the UI by adding the @Name annotation.

The implementation of the Action

The Constructor of the implementation of the Action will take the configuration as an argument and make the configurations available.

public TimeDenyAuthenticationAction(TimeDenyAuthenticationActionConfiguration configuration)

The AuthenticationAction’s apply method applies the action after authentication has taken place. This is the implementation of the actual Action and where the desired logic takes place.

The apply method itself runs silently and the outcome could be that the action returns a success, failed or pending result.

A success result is just that, the authentication action executes its logic successfully and moves to the next instance that could be another action or that the authenticated session is created.

A failed result means that no authenticated session is created and the user is denied access. This would redirect back to the client and can return an error description as well as an Error Code.

If the pending result is returned then the action did not fail or succeed, it is in a state that is pending and requires completion. The apply method can be called several times and thus might not fully complete and the state is instead set to pending and would succeed or fail on a subsequent call. In these types of cases it could be useful to keep track of a transactionId in order to know if the execution of the apply method is from the same flow or a different flow. This is especially important if the action is acting on external resources to make sure these are not unintentionally invoked several times.

@Override
public AuthenticationActionResult apply(
        AuthenticationAttributes authenticationAttributes,
        AuthenticatedSessions authenticatedSessions,
        String authenticationTransactionId,
        AuthenticatorDescriptor authenticatorDescriptor)

This example is an Action that will allow or deny access after authentication based on a schedule. A couple of time comparisons are done to determine if access should be allowed or denied and the configured time zone is taken into account.

If it is determined that access should be allowed the apply method returns a successfulResult where the attributes from the authentication are passed as an argument. In this examples the attributes have not been modified but they could have been. Since Authentication Actions can be chained the authenticationAttributes are passed on and made available to the next Action in the chain or to the authenticated session.

Similarly, if the checks fail and its determined that access should be denied, the failedResult method is invoked and returns the reason for the denial.

{
    if (_timeComparer.isNowAfterStartTime())
    {
        if (_timeComparer.isNowBeforeEndTime())
        {
            _logger.debug("Access allowed based on time");

            return AuthenticationActionResult.successfulResult(authenticationAttributes);
        }
        else if (_logger.isDebugEnabled())
        {
            ZoneId zoneId = _timeComparer.getZoneId();

            _logger.debug(
                    "Access denied because current time {} in timezone {} is after the configured allowed end time {}",
                    _timeComparer.getNow().atZone(zoneId), zoneId, _timeComparer.getEndTime());
        }
    }
    else if (_logger.isDebugEnabled())
    {
        ZoneId zoneId = _timeComparer.getZoneId();

        _logger.debug(
                "Access denied because current time {} in timezone {} is after the configured allowed start time {}",
                _timeComparer.getNow().atZone(zoneId), zoneId, _timeComparer.getStartTime());
    }

    return AuthenticationActionResult.failedResult("Access denied based on time");
}

Conclusion

Developing an Authentication Action is fairly trivial. It is a powerful way to inject logic into the authentication flow and very complex scenarios can be handled by chaining multiple Actions together.

The Maven Archetype is a quick way to get the basic structure of the plugin generated in a templatized way so that focus and tie can be spent on the actual implementation of the logic rather than the surrounding building blocks.

Resources

Let’s Stay in Touch!

Get the latest on identity management, API Security and authentication straight to your inbox.

Keep up with our latest articles and how-tos using RSS feeds