The authn/Password login flow supports an extensible set of back-ends for password-based authentication, normally collected using a web form, and is the flow used at least in part by most deployments.
It is compatible with non-browser clients by virtue of supporting HTTP Basic authentication if credentials are provided without prompting, and knows not to present a form when a non-browser profile like ECP is used.
The IdP includes a dedicated plugin API for developing custom "back-ends", the CredentialValidator interface and some associated base classes for assisting in developing new ones. The flow also supports chaining of CredentialValidator plugins in arbitrary sequences instead of requiring the use of JAAS login modules for that feature.
Enabling Module
Configuring and using this feature requires that you first enable the "idp.authn.Password" module if it isn't already enabled. Systems upgraded from older releases generally come pre-enabled due to the prior state of the configuration tree.
Aside from the more specific back-end configurations, there are beans and properties defined for some general configuration independent of the back-end chosen. They are all listed in the reference section below.
The most important bean is shibboleth.authn.Password.Validators, a List of CredentialValidator plugins that should be used to validate the subject's username and password.
A property named idp.authn.Password.requireAll controls whether the credentials must be validated successfully by all applicable plugins in the list, or just one.
Combining plugins tends to be more complex but the default, requiring only a single valid result, is fairly simple to deal with. One obvious issue is that you should make sure to set the property named idp.authn.Password.removeAfterValidation to false if you apply the "require all" mode to prevent the username/password information from being pulled mid-request.
Basic Features
The first user interface layer of the flow is actually HTTP Basic authentication; if a header with credentials is supplied, the credentials are tested immediately with no prompting. If that step fails, or no credentials are supplied, then a view is rendered unless the request is for passive authentication or part of a non-browser profile.
Views are handled by Spring Web Flow, so support various technologies, but Velocity and JSP are supported by default and the examples are all based on Velocity.
Be aware that the default view templates and configuration of classified messages (see later section on Errors and Warnings) results in a system that will report context-sensitive errors to the user, such as whether the password was invalid or the username was mis-entered. This is appropriate for most organizations to reduce help desk calls caused by simple user error, but some organizations keep usernames a secret and may wish to adjust the configuration to collapse all error reporting and avoid leaking information about whether usernames are valid or not.
Example Velocity User Interface
The example Velocity templates views/login.vm and views/login-error.vm illustrate generally how to populate the form, and how to detect and respond to error conditions. Internationalization is performed through the use of Spring message properties (which can be overridden via the top-level messages folder). Information on Spring internationalization is near the end of this section of the Spring documentation.
When rendering Velocity views, several variables are available to aid per-relying party customization.
Spring form generation macros such as #springMessage and #springMessageText are available to Velocity templates.
You can freely comment out or remove the "Do Not Cache" support of course, or use Javascript to automate it for certain address ranges.
Error Handling 5.1
This material replaces the historical behavior, which was to supply a login-error.vm template that was rendered to directly produce the error displayed to the user. The new approach is more concise, faster, and friendlier to other template languages, but deployers remain free to do whatever they prefer.
By default, a Java class is supplied and installed that implements a standard form of “analysis” of the state of the request to produce a plausible error message to display to the user. This is suitable for routine cases, and is called by the default login view template via the standard variable “errorMessageFunction”.
The idp.authn.Password.errorMessageFunction property can be set to a custom bean definition of type Function<ProfileRequestContext,String> to do their own error handling, including via scripting, etc.
Older/Advanced Error Handling Example
The error message classification feature allows error messages to be mapped into grouped "classes" of errors that can be used in the view to report the results of a failed login. The main value of this feature is in supporting chains of multiple validators because the system will accumulate all of the classified errors that occur so that a precedence of error types can be applied to decide what to report to the user. A working example of this dating back to the older view-centric model:
views/login-error.vm
## Velocity Template for login error message production, included by login.vm
##
## authenticationErrorContext - context containing error data, if available
##
#if ($authenticationErrorContext && $authenticationErrorContext.getClassifiedErrors().size() > 0 && $authenticationErrorContext.getClassifiedErrors().iterator().next() != "ReselectFlow")
## This handles errors that are classified by the message maps in the config.
#if ($authenticationErrorContext.getClassifiedErrors().contains("InvalidPassword"))
#set ($eventId = "InvalidPassword")
#elseif ($authenticationErrorContext.getClassifiedErrors().contains("AccountLocked"))
#set ($eventId = "AccountLocked")
#elseif ($authenticationErrorContext.getClassifiedErrors().contains("AccountDisabled"))
#set ($eventId = "AccountDisabled")
#elseif ($authenticationErrorContext.getClassifiedErrors().contains("ExpiredPassword"))
#set ($eventId = "ExpiredPassword")
#elseif ($authenticationErrorContext.getClassifiedErrors().contains("UnknownUsername"))
#set ($eventId = "UnknownUsername")
#end
#elseif ($authenticationErrorContext && $authenticationErrorContext.getExceptions().size() > 0)
## This handles login exceptions that are left unclassified.
#set ($loginException = $authenticationErrorContext.getExceptions().get(0))
#if ($loginException.getMessage())
#set ($message = "Login Failure: $loginException.getMessage()")
#else
#set ($message = "Unidentified error")
#end
#end
#if ($eventId || $message)
<div class="error notification">
#if ($eventId == "AccountLocked")
## your code here
#elseif ($eventId == "AccountDisabled")
## your code here
#elseif ($eventId == "ExpiredPassword")
## your code here
#elseif ($eventId == "InvalidPassword")
## your code here
#elseif ($eventId == "UnknownUsername")
## your code here
#elseif ($message)
## your code here
#end
</div>
#end
JSP User Interface
The use of JSP is not advised, but is supported. To do so, views/login.vm must be removed or renamed and the IdP restarted, or it will take precedence. Views in JSP should be created in edit-webapp/WEB-INF/jsp (and the warfile should be recreated with bin/build.sh or bin/build.bat and the container restarted). The old V2 Taglibs are supported for JSP for now, but we have plans to deprecate them in the future.
There are parent beans defined for each back-end plugin supported to allow easy incorporation of the default configured behavior into the chain of validators. There are "global" options for each back-end that will apply to any validator of the given type unless overridden.
For example, this syntax defines a single LDAP validator that uses settings defined in a manner consistent with older versions of the software. The parent bean is used "as is" with no changes.
This syntax in contrast uses the parent bean as a template:
In this case, the LDAP authenticator object used is explicitly set to the bindSearchAuthenticator bean instead of the value set by the "idp.authn.LDAP.authenticator" property.
This is a simple example but the point is that once you switch from a <ref> to a new, but inherited, <bean>, you can default or override each property of the validator as you choose. The global/default settings in the individual Spring bean files only matter to the extent that you want to use them.
There is support for case folding, trimming, and regular expression transformation of the username entered by a subject. The same features can be applied either globally or individually to each validator so that the normalized value may be different in each case, as required.
The globally defined properties are used if not overridden, but options exist on each supported CredentialValidator class to override a global setting with a more specific setting.
Note that the result of a successful authentication via a particular validator will typically produce a Java Subject containing a UsernamePrincipal containing the normalized value specific to that validator, so it's possible when chaining and using the "RequireAll" feature to produce compound results containing multiple, distinct UsernamePrincipal objects that will not work with the default/simple SimplePostLoginC14NConfiguration used in many cases. Something more may be needed to tell the system which of the potentially multiple "usernames" should be the official one used in the session.
Similarly, the property idp.authn.Password.matchExpression is applied globally, if set, but each CredentialValidator can be individually configured with their own matchExpression setting. If a validator detects that a candidate transformed/normalized username doesn't match the expression, the validator will silently be ignored. This is not considered a success or failure. Note that Spring will automatically convert a string into a regular expression pattern, so adding a localized match rule is as simple as:
A special feature of this flow is the ability to inject your own behavior in response to particular error or warning conditions that occur. These conditions are set via the shibboleth.authn.Password.ClassifiedMessageMap bean in authn/password-authn-config.xml. If you map a particular string label to one or more exception/error messages in the map, that string label becomes a flow "event" that you can program the flow to respond to.
If your goal is to terminate processing at this point, then you should take a look at the discussion in AuthenticationConfiguration under "Custom Events", which describes how to configure the system to treat your custom events as allowable "end" states for the request and handle them as errors.
If your goal is rather to interrupt but then resume the login flow, then this is somewhat accomodated with a predefined set of flow defintions designed to be user-editable in flows/authn/conditions/, but this is a fairly exotic approach and is not advisable in most cases.
The conditions-flows.xml file controls what events are detected and handled, and what to do. The example included responds to a number of events by calling predefined subflows that are themselves just empty examples that return immediately, and then control passes back to the view state that renders the login form.
An obvious use for this feature is responding to an imminently expiring password with some kind of warning view; another is detection of a locked account, and the use of a dedicated view to help the user with that condition.
In the simplest case, maybe you just want to display a page, in which case you can insert your own <view-state> that displays a template of your own creation. Views generally have to contain a form whose action is set to "$flowExecutionUrl" and that contain a button to submit the form with the value "_eventId_proceed". Your view then transitions on "proceed" to the corresponding <end-state> and ends up back on the login form.
In more complex cases, you may need to actually redirect the user out to other functionality implemented outside the IdP, which is something the Spring Web Flow documentation refers to as an external redirect, and it's possible to resume the flow in that scenario as well. If you don't, the user's session is suspeneded and will consume resources until it times out, and that doesn't work well at scale, so you have to be careful with that approach.
Advanced Features
A pluggable implementation of account lockout can be enabled by defining a bean called shibboleth.authn.Password.AccountLockoutManager that implements the AccountLockoutManager interface. A default implementation of this interface is available using a parent bean named shibboleth.StorageBackedAccountLockoutManager (commented out by default in conf/authn/password-authn-config.xml).
The default implementation has the following behavior:
Tracks lockout state via an injected StorageService, by default the in-memory variant preconfigured with the IdP. This results in per-node lockout tracking, usually sufficient considering the overhead of other options.
Provides a simple algorithm that tracks invalid attempts that occur within a specified interval, and once a limit is reached, blocks subsequent attempts for a specified duration.
Scopes lockouts to a given username and client IP address (but this is configurable via an injectable function).
Note that each attempt can be separated in time by the specified interval, so a 5 minute interval does not mean all the attempts must occur within 5 minutes, but could occur over a period as long as 5 times the number of attempts.
Once an account locks, an "AccountLocked" event is signaled, and the new default configuration maps this event to the "AccountLocked" classified error. If your installation pre-dates this feature, you will likely want to map that event to whichever classified error reporting condition you want to expose to the user.
See AccountLockoutManagement for information on the administrative flow that allows management of lockout state.
Reference
Notes
The idp.authn.Password.retainAsPrivateCredential property should be used with caution, as it retains the password and makes it available in plaintext form within server memory at various stages. When the session is written to storage, the password is encrypted with the secret key used by the IdP for other encryption of data to itself, but it will be decrypted and back in memory at various times when the session is accessed or updated.