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
- An installation of the Curity Identity Server
- A configured Authenticator, for example the simple Username Password Authenticator
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.
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
.
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.
@Overridepublic String getPluginImplementationType(){return "time-deny";}
All plugins must provide an interface that extends Configuration
representing its runtime configuration. This is done via the PluginDescriptor
.
@Overridepublic 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.
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. 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.
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.
@Overridepublic 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.
{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