...
Code Block |
---|
(Windows)
C:\opt\shibboleth-idp> bin\module.bat -t idp.authn.MFA || bin\module.bat -e idp.authn.MFA
(Other)
$ bin/module.sh -t idp.authn.MFA || bin/module.sh -e idp.authn.MFA |
General Configuration
Localtabgroupexpand |
---|
Localtab live |
---|
|
Use authn/mfa-authn-config.xml to configure this flow. Localtab live |
---|
| active | true
---|
Expand |
---|
|
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. |
...
Simple sequence of factors Flow1 and Flow2
Expand |
---|
Code Block |
---|
| <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> |
|
The "combine the results" behavior in step 3 above actually comes from a built-in bean that you can override for more customized behavior. A bean named shibboleth.authn.MFA.resultMergingStrategy will be used to supply a function to run to merge together all of the authentication results produced by an entire sequence of steps in a customized way (this is discussed in more detail below).
...
Conditional use of two factors, Flow1 and Flow2
Expand |
---|
Code Block |
---|
| <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> |
|
There's a fair amount going in on the script, but it's mostly just navigating and populating contexts into the tree, and the whole middle section is doing the attribute lookup and check.
...
Example with a method selection UI
Expand |
---|
Code Block |
---|
| <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 example above is geared around using a dedicated custom webflow to offer a selection UI, so such a flow controls its own rules for what events it can signal. If you wanted to, for example, stick a button or link on the login form used by the Password login flow and signal that back to the MFA logic as a custom event, you will need to modify conf/authn/authn-events-flow.xml (see the general discussion under Custom Events).
...
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:
Localtabgroupexpand |
---|
Localtab live |
---|
|
authn/general-authn.xml Code Block |
---|
| ...
<bean id="authn/MFA" parent="shibboleth.AuthenticationFlow"
p:passiveAuthenticationSupported="true"
p:forcedAuthenticationSupported="true"
p:reuseCondition="false">
...etc...
</bean>
... |
Localtab live |
---|
|
active | true |
---|
Expand |
---|
|
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
Localtabgroupexpand |
---|
Localtab live |
---|
|
The beans defined in authn/mfa-authn-config.xml follow: Localtab live |
---|
|
active | true |
---|
Expand |
---|
|
The beans defined in authn/mfa-authn-config.xml follow: localtab-live |
Expand |
---|
|
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 |
---|
| <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 |
Expand |
---|
title | Flow Descriptor XML (V4.1+) |
---|
|
To replace the internally defined flow descriptor bean, the following XML is required: Code Block |
---|
| <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. |
...