Simple Authentication Action

Simple Authentication Action

On this page

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 has not yet produced an authenticated session, which later can be used for Single Sign-On etc.

There are different Authentication Actions available in the Curity Identity Server out-of-the-box. The Actions on their own can handle many 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

Attributes are grouped into AuthenticationAttributes and AuthenticationActionAttributes. AuthenticationAttributes include attributes related to the authentication (context-attributes) and authenticated subject (subject-attributes). This group of attributes normally end up in tokens. AuthenticationActionAttributes - also referred to as "Action Attributes" - on the other hand, are an internal set of temporary attributes. Their purpose is to enable communication between Actions. Action attributes are deleted at the end of an authentication process and thus do not end up in tokens. Actions have access to both types of attributes.

Any Authenticator can be configured with any number of Actions to run after login, or after SSO. They are executed in the configured order. In other words, Actions 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 plugin types to quickly generate a skeleton of a given plugin for the Curity Identity Server.

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.

shell
12345678
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 from the above command 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. The server 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.

java
1
public final class TimeDenyAuthenticationActionPluginDescriptor implements AuthenticationActionPluginDescriptor<TimeDenyAuthenticationActionConfiguration>

The Authentication Action Descriptor's getPluginImplementationType method returns the plugin implementation type. The value is a simple string that must be unique across all the plugins that are deployed to the Curity Identity Server.

java
12345
@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.

java
12345
@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 the field is defined as a Boolean datatype it will be an on/off switch type UI element.

java
1
Boolean isEnabled();

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

java
1
TimeConfiguration getNoAccessBefore();

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

java
123456789
@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. When defining an optional configuration parameter, you must provide a default value.

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.

java
1
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 the AuthenticationTransactionId 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.

java
12
@Override
public AuthenticationActionResult apply(AuthenticationActionContext context)

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 example the attributes have not been modified, but they could have been in a different example. 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.

java
1234567891011121314151617181920212223242526272829
{
if (_timeComparer.isNowAfterStartTime())
{
if (_timeComparer.isNowBeforeEndTime())
{
_logger.debug("Access allowed based on time");
return AuthenticationActionResult.successfulResult(context.getAuthenticationAttributes());
}
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 time can be spent on the actual implementation of the logic rather than the surrounding building blocks.

Resources

Join our Newsletter

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

Start Free Trial

Try the Curity Identity Server for Free. Get up and running in 10 minutes.

Start Free Trial