Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Current File(s): conf/authn/mfa-authn-config.xml, conf/authn/authn.properties (V4.1+)
Format: Native Spring, Properties (V4.1+)

Table of Contents

Overview

The authn/MFA login flow is a special "composite" mechanism that provides a scriptable (or programmable) way of combining other login flows to produce simple or complex sequences of login challenges that combine to provide stronger or more flexible authentication options than individual methods can provide on their own.

...

General Configuration

Localtabgroup
Localtab live
titleV4.0

Use authn/mfa-authn-config.xml to configure this flow.

Localtab live
activetrue
titleV4.1+

Most of the flow configuration is in authn/mfa-authn-config.xml but some generic settings applicable to all login flows are in authn/authn.properties.

Tip

Note that when you use the MFA flow, it's common that it will be the only flow enabled via the idp.authn.flows property. In particular, any flows you direct the MFA flow to run via rules and scripts should not be enabled themselves because to do so may cause the IdP to run them itself in ways that are likely to subvert your intent.

...

A bean called shibboleth.authn.MFA.TransitionMap contains the map of rules to run to control the transition between flows and determine when to complete work. This is a map whose keys are the flows to "exit" from, and whose values are a bean of the class type net.shibboleth.idp.authn.MultiFactorAuthenticationTransition (a parent bean called shibboleth.authn.MFA.Transition is provided). The first rule is marked with an empty or null key value in the map.

...

Consider a simple example that implements this sequence:

  1. Run the "authn/Flow1" flow.

  2. If Step 1 succeeds, run the "authn/Flow2" flow.

  3. If Step 2 succeeds, combine the results of the two flows into one.

  4. If either Step 1 or 2 fails, return that failure as the MFA flow result.

This simple example doesn't require any logic or scripting:

...

Simple sequence of factors Flow1 and Flow2
collapse
Code Block
languagetruexml
<util:map id="shibboleth.authn.MFA.TransitionMap">
	<!-- Run authn/Flow1 first. -->
	<entry key="">
		<bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Flow1" />
	</entry>

	<!-- If that returns "proceed", run authn/Flow2 next. -->
	<entry key="authn/Flow1">
		<bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Flow2" />
	</entry>
        
	<!-- An implicit final rule will return whatever the second flow returns. -->
</util:map>

...

This relatively complex example relies on a single script that does a number of interesting things to achieve the following sequence:

  1. Run the "authn/Flow1" flow.

  2. If Step 1 succeeds and the result is sufficient to satisfy the request, resolve an attribute about the user identified by Step 1.

  3. If the result from Step 1 is sufficient AND the attribute resolved indicates that a user may use that method alone, then finish with the result from Step 1.

  4. If the result from Step 1 is not sufficient OR the attribute resolved indicates that an additional factor is required, run the "authn/Flow2" flow.

  5. If successful, combine the results from the two flows into one.

  6. If either method fails, return that failure as the MFA flow result.

...

...

Conditional use of two factors, Flow1 and Flow2
Code Block
collapselanguagetruexml
<util:map id="shibboleth.authn.MFA.TransitionMap">
	<!-- Run authn/Flow1 first. -->
	<entry key="">
		<bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Flow1" />
	</entry>

	<!--
	Second rule runs a function if authn/Flow1 succeeds, to determine whether an additional
	factor is required.
	-->
	<entry key="authn/Flow1">
		<bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkSecondFactor" />
	</entry>

	<!-- An implicit final rule will return whatever the second flow returns. -->
</util:map>

<!-- Example script to see if second factor is required. -->
<bean id="checkSecondFactor" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript"
		p:customObject-ref="shibboleth.AttributeResolverService">
	<constructor-arg>
		<value>
        <![CDATA[
            nextFlow = "authn/Flow2";

            // Go straight to second factor if we have to, or set up for an attribute lookup first.
            authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext");
            mfaCtx = authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext");
            if (mfaCtx.isAcceptable()) {
                // Attribute check is required to decide if first factor alone is enough.
                resCtx = input.getSubcontext(
                    "net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext", true);
                
				// Look up the username
                usernameLookupStrategyClass = Java.type("net.shibboleth.idp.session.context.navigate.CanonicalUsernameLookupStrategy");
                usernameLookupStrategy = new usernameLookupStrategyClass();
                resCtx.setPrincipal(usernameLookupStrategy.apply(input));


				// resolve the attribute to determine if a first factor is sufficient
                resCtx.getRequestedIdPAttributeNames().add("allowedLoginMethods");
                resCtx.resolveAttributes(custom);
                
                // Check for an attribute value that authorizes use of first factor.
                attribute = resCtx.getResolvedIdPAttributes().get("allowedLoginMethods");
                valueType =  Java.type("net.shibboleth.idp.attribute.StringAttributeValue");
                if (attribute != null && attribute.getValues().contains(new valueType("Flow1"))) {
                    nextFlow = null;
                }
                
                input.removeSubcontext(resCtx);   // cleanup
            }
            
            nextFlow;   // pass control to second factor or end with the first
        ]]>
	</value>
	</constructor-arg>
</bean>

...

For full control, you must build a map bean and set it as the value of the nextFlowStrategyMap property of a transition bean. The keys to the map are the events to "catch", and the values of the map can either be a simple String (a subflow to run) or a Function<ProfileRequestContext,String> to run to return the flow to run.

...

As an example, if you built a view-centric flow that signaled events indicating the type of authentication to perform, it might look something like the following:

...

...

Example with a method selection UI
collapse
Code Block
languagetruexml
<util:map id="shibboleth.authn.MFA.TransitionMap">
	<!-- Run authn/Flow1 first. -->
	<entry key="">
		<bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Flow1" />
	</entry>

	<!-- Second rule calls a flow to display a view to select a method to run. -->
	<entry key="authn/Flow1">
		<bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="custom/methodChooser" />
	</entry>

	<!-- Third rule decides what to call based on event signaled by the view. -->
	<entry key="custom/methodChooser">
		<bean parent="shibboleth.authn.MFA.Transition">
			<property name="nextFlowStrategyMap">
				<map>
					<!-- Maps event to a flow -->
					<entry key="ChooseMethodA" value="authn/FlowA" />
					<entry key="ChooseMethodB" value="authn/FlowB" />

					<!-- Maps event to a scripted function bean reference-->
					<entry key="EventToHandle" value-ref="eventHandlerScript" />
				</map>
			</property>
		</bean>
	</entry>	

	<!-- An implicit final rule will return whatever the chosen flow returns. -->
</util:map>

<!-- Event Handler Script -->
<bean id="eventHandlerScript" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript">
    <constructor-arg>
	    <value>
		    <![CDATA[
			...
		    ]]>
		</value>
    </constructor-arg>
</bean>

...

The IdP expects/requires a login flow to produce a single AuthenticationResult when it finishes work successfully. To avoid a total redesign, the MFA flow is built to maintain this constraint by combining the individual results it may accumulate in the course of executing into one final result. It does this using a default function that can be overridden by defining a bean named shibboleth.authn.MFA.resultMergingStrategy of type Function<ProfileRequestContext,AuthenticationResult> (i.e., a function that returns the result to use).

It is hoped that the default behavior will be sufficient for most people, which is to perform a true merge of all of the Principals, and public and private Credentials contained in the individual Subjects across all of the active results contained in the MultiFactorAuthenticationContext that tracks the overall state of the request. In addition, to enable the SSO behavior described below, the default function actually wraps the individual results into a special custom Principal type that allows the MFA login flow to actually recover the individual results that it obtained earlier and use them to avoid re-prompting a user with factors that have already been performed, just as the IdP itself does.

...

Unless a request includes forced re-authentication, any active/previous results produced and tracked by the MFA flow will be reused under these conditions:

  • the login flow is known to the system (i.e., it hasn't been removed with no descriptor bean left in place)

  • the result remains within the lifetime of the login flow, per policy

You will note this does not include a check for whether the result is specifically applicable to the request's requirements, because it assumes you are in control of that decision if you choose to run the flow.

...

You can split these concerns any way you prefer, but if you can include at least some of your logic in the reuse condition rather than the MFA logic itself, that can improve efficiency. But in the simplest case, if you want the MFA rules to run on every request no matter what:

authn/general-authn.xml
Localtabgroup
Localtab live
titleV4.0
Code Block
languagexml
title
Code Block
languagexml
...
		<bean id="authn/MFA" parent="shibboleth.AuthenticationFlow"
				p:passiveAuthenticationSupported="true"
				p:forcedAuthenticationSupported="true"
				p:reuseCondition="false">

			...etc...

		</bean>
...
Localtab live
activetrue
titleV4.1+
Code Blocktitle
authn/authn.properties
Code Block
...
idp.authn.MFA.reuseCondition = shibboleth.Conditions.FALSE
...


...

For more advanced cases or to improve efficiency, a bean can be defined for a script or Java logic that defines the condition to evaluate to decide on reuse, and you can attach that bean by name.

Reference

Localtabgroup
Localtab live
titleBeans (V4.0)

The beans defined in authn/mfa-authn-config.xml follow:

Bean ID / Type

Default

Description

shibboleth.authn.MFA.TransitionMap

Map<String,MultiFactorAuthenticationTransition>


Static ruleset containing the starting point for MFA execution and the rules to use to decide how to do work

shibboleth.authn.MFA.TransitionMapStrategy

Function<ProfileRequestContext,Map<String,MultiFactorAuthenticationTransition>


Optional function bean to return the ruleset to use instead of using a static ruleset

shibboleth.authn.MFA.Transition

MultiFactorAuthenticationTransition


Parent bean for defining transition rules in the values of the previous bean's map entries

shibboleth.authn.MFA.validateLoginTransitions

Boolean

true

Whether login flows should only be run with regard for forceAuthn/isPassive/nonBrowser (and similar) conditions

shibboleth.authn.MFA.resultMergingStrategy

Function<ProfileRequestContext,AuthenticationResult>

described above

Function to run to produce final merged result of MFA login flow during successful completion

shibboleth.authn.MFA.resultCachingPredicate

Predicate<ProfileRequestContext>


An optional bean that can be defined to control whether to preserve the authentication result in an IdP session

Localtab live
activetrue
titleBeans (V4.1+)

The beans defined in authn/mfa-authn-config.xml follow:

Bean ID / Type

Default

Description

shibboleth.authn.MFA.TransitionMap

Map<String,MultiFactorAuthenticationTransition>


Static ruleset containing the starting point for MFA execution and the rules to use to decide how to do work

shibboleth.authn.MFA.TransitionMapStrategy

Function<ProfileRequestContext,Map<String,MultiFactorAuthenticationTransition>


Optional function bean to return the ruleset to use instead of using a static ruleset

shibboleth.authn.MFA.Transition

MultiFactorAuthenticationTransition


Parent bean for defining transition rules in the values of the previous bean's map entries

shibboleth.authn.MFA.resultMergingStrategy

Function<ProfileRequestContext,AuthenticationResult>

described above

Function to run to produce final merged result of MFA login flow during successful completion

shibboleth.authn.MFA.resultCachingPredicate

Predicate<ProfileRequestContext>


An optional bean that can be defined to control whether to preserve the authentication result in an IdP session

Localtab live
titleProperties (V4.1+)

Properties specific to this flow defined in authn/authn.properties are:

Name

Default

Description

idp.authn.MFA.validateLoginTransitions

true

Whether login flows should only be run with regard for forceAuthn/isPassive/nonBrowser (and similar) conditions

The general properties configuring this flow via authn/authn.properties are:

Name

Default

Description

idp.authn.MFA.order

1000

Flow priority relative to other enabled login flows (lower is "higher" in priority)

idp.authn.MFA.nonBrowserSupported

true

Whether the flow should handle non-browser request profiles (e.g., ECP)

idp.authn.MFA.passiveAuthenticationSupported

true

Whether the flow allows for passive authentication

idp.authn.MFA.forcedAuthenticationSupported

true

Whether the flow supports forced authentication

idp.authn.MFA.proxyRestrictionsEnforced

%{idp.authn.enforceProxyRestrictions:true}

Whether the flow enforces upstream IdP-imposed restrictions on proxying

idp.authn.MFA.proxyScopingEnforced

false

Whether the flow considers itself to be proxying, and therefore enforces SP-signaled restrictions on proxying

idp.authn.MFA.discoveryRequired

false

Whether to invoke IdP-discovery prior to running flow

idp.authn.MFA.lifetime

%{idp.authn.defaultLifetime:PT1H}

Lifetime of results produced by this flow

idp.authn.MFA.inactivityTimeout

%{idp.authn.defaultTimeout:PT30M}

Inactivity timeout of results produced by this flow

idp.authn.MFA.reuseCondition

shibboleth.Conditions.TRUE

Bean ID of Predicate<ProfileRequestContext> controlling result reuse for SSO

idp.authn.MFA.activationCondition

shibboleth.Conditions.TRUE

Bean ID of Predicate<ProfileRequestContext> determining whether flow is usable for request

idp.authn.MFA.subjectDecorator


Bean ID of BiConsumer<ProfileRequestContext,Subject> for subject customization

idp.authn.MFA.supportedPrincipals

(see below)

Comma-delimited list of protocol-specific Principal strings associated with flow

idp.authn.MFA.addDefaultPrincipals

false

Whether to auto-attach the preceding set of Principal objects to each Subject produced by this flow

As a generic flow, the supportedPrincipals property defaults to the following XML:

Code Block
languagexmlcollapsetrue
<list>
    <bean parent="shibboleth.SAML2AuthnContextClassRef"
        c:classRef="urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol" />
    <bean parent="shibboleth.SAML2AuthnContextClassRef"
        c:classRef="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" />
    <bean parent="shibboleth.SAML2AuthnContextClassRef"
        c:classRef="urn:oasis:names:tc:SAML:2.0:ac:classes:Password" />
    <bean parent="shibboleth.SAML1AuthenticationMethod"
        c:method="urn:oasis:names:tc:SAML:1.0:am:password" />
</list>

In property form, this is expressed as:

Code Block
idp.authn.MFA.supportedPrincipals = \
    saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol, \
    saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport, \
    saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:Password, \
    saml1/urn:oasis:names:tc:SAML:1.0:am:password

This default is not intended to be applicable to most systems, but matches the behavior of the flow's default/example configuration which accounts for both the IPAddress and Password flows being combined.

Localtab live
titleFlow Descriptor XML (V4.1+)

To replace the internally defined flow descriptor bean, the following XML is required:

Code Block
languagexml
<util:list id="shibboleth.AvailableAuthenticationFlows">
 
    <bean p:id="authn/MFA" parent="shibboleth.AuthenticationFlow"
            p:order="%{idp.authn.MFA.order:1000}"
            p:nonBrowserSupported="%{idp.authn.MFA.nonBrowserSupported:true}"
            p:passiveAuthenticationSupported="%{idp.authn.MFA.passiveAuthenticationSupported:true}"
            p:forcedAuthenticationSupported="%{idp.authn.MFA.forcedAuthenticationSupported:true}"
            p:proxyRestrictionsEnforced="%{idp.authn.MFA.proxyRestrictionsEnforced:%{idp.authn.enforceProxyRestrictions:true}}"
            p:proxyScopingEnforced="%{idp.authn.MFA.proxyScopingEnforced:false}"
            p:discoveryRequired="%{idp.authn.MFA.discoveryRequired:false}"
            p:lifetime="%{idp.authn.MFA.lifetime:%{idp.authn.defaultLifetime:PT1H}}"
            p:inactivityTimeout="%{idp.authn.MFA.inactivityTimeout:%{idp.authn.defaultTimeout:PT30M}}"
            p:reuseCondition-ref="#{'%{idp.authn.MFA.reuseCondition:shibboleth.Conditions.TRUE}'.trim()}"
            p:activationCondition-ref="#{'%{idp.authn.MFA.activationCondition:shibboleth.Conditions.TRUE}'.trim()}"
            p:subjectDecorator-ref="#{getObject('%{idp.authn.MFA.subjectDecorator:}'.trim())}">
        <property name="supportedPrincipalsByString">
            <bean parent="shibboleth.CommaDelimStringArray"
                c:_0="#{'%{idp.authn.MFA.supportedPrincipals:}'.trim()}" />
        </property>
    </bean>
 
</util:list>

In older versions and upgraded systems, this list is defined in conf/authn/general-authn.xml. In V4.1+, no default version of the list is provided and it may simply be placed in conf/global.xml if needed.

...