The Shibboleth IdP V3 software has reached its End of Life and is no longer supported. This documentation is available for historical purposes only. See the IDP4 wiki space for current documentation on the supported version.
Replicating Multi-Context Broker Functionality (Duo + Username/Password with user-opt-in forcing Duo)
This document is only applicable to versions prior to 3.3. Do not try to follow this document if you are using IdPv3.3 or greater.
This document describes how the University of Chicago configured their Identity Provider under IdPv3 to behave like it did under IdPv2 with the MCB installed. In our installation all users are forced to use Password for initial authentication. Then users can opt-in to having Duo Security layered on top of their initial authentication.
Note: There are two Duo plugins for Shibboleth v3
University of Chicago uses a Duo plugin developed by Unicon. Later, Duo Security Inc released their own Shibboleth v3 plugin; see Contributions and Extensions. Duo's plugin cannot (easily) support the full MCB functionality described on this page, but it might have some other advantages. If you're shopping around, look at both.
Step-by-step guide
- Download and install/configure the Unicon Duo Security login flow from GitHub.
Create a new webflow
/opt/shibboleth-idp/flows/authn/Duo/duo-authn-flow.xml
. The contents are below. The changes we made ensure that on a forceAuthn request, the user is prompted to also enter their username-password again (so long as it's not their first pass through the IdP – determined by a 2-minute timer from when the IdPSession is created).<flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd" parent="authn.abstract, authn/conditions"> <var name="duoCredential" class="net.unicon.iam.shibboleth.idp.authn.duo.authn.DuoCredential"/> <var name="currentTime" class="java.util.Date" /> <!-- if the IdP session is < 2 minutes then don't check for forceAuthn. If it's more than that then check for forceAuthn and if forceAuthn is in play make the user do a 2nd Password login. This eliminates the problem we had with a user hitting a forceAuthn site as their first SP & then having to double-enter their username/password. --> <decision-state id="checkFirstLogin"> <if test="(currentTime.getTime() - opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.session.context.SessionContext)).getIdPSession().getCreationInstant()) > 120000" then="checkForceAuth" else="duo" /> </decision-state> <decision-state id="checkForceAuth"> <if test="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).isForceAuthn()" then="CallPasswordFlow" else="duo" /> </decision-state> <subflow-state id="CallPasswordFlow" subflow="authn/Password"> <input name="calledAsSubflow" value="true"/> <transition on="proceed" to="duo"/> </subflow-state> <view-state id="duo" view="duo" model="duoCredential"> <binder> <binding property="signedDuoResponse"/> </binder> <on-render> <evaluate expression="environment" result="viewScope.environment"/> <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext))" result="viewScope.authenticationContext"/> <set name="viewScope.request" value="flowRequestContext.getExternalContext().getNativeRequest()"/> <set name="viewScope.sigRequest" value="duoAuthenticationService.generateSignedRequestToken(authenticationContext)"/> <set name="viewScope.apiHost" value="duoAuthenticationService.getApiHost()"/> </on-render> <transition on="proceed" bind="true" validate="false" to="validateDuo"/> </view-state> <action-state id="validateDuo"> <evaluate expression="ValidateDuoResponse"/> <evaluate expression="'proceed'"/> <transition on="proceed" to="proceed"/> </action-state> </flow>
Setup
global.xml
to use our local Duo AuthnContextClass instead of the default from the pluginglobal.xml config<bean id="authn/Duo" parent="shibboleth.AuthenticationFlow" p:nonBrowserSupported="false" p:forcedAuthenticationSupported="%{duo.forcedAuthenticationSupported:true}"> <property name="supportedPrincipals"> <util:list> <bean parent="shibboleth.SAML2AuthnContextClassRef" c:classRef="http://uchicago.edu/duo" /> <bean parent="shibboleth.SAML2AuthnContextClassRef" c:classRef="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" /> <bean parent="shibboleth.SAML1AuthenticationMethod" c:method="http://uchicago.edu/duo" /> </util:list> </property> </bean>
- Download and install the University of Chicago AuthnClassPredicate IdPv3 extension from GitHub.
Setup the attribute-resolver to configure an attribute to release what AuthnClassContexts a user is allowed to use. We used eduPersonAssurance. In our example, we push Grouper groups to our LDAP server & then use a MappedAttribute to translate the Grouper values to AuthnContextClass values
Example eduPersonAssurance attribute<resolver:AttributeDefinition xsi:type="Mapped" xmlns="urn:mace:shibboleth:2.0:resolver:ad" id="eduPersonAssurance" sourceAttributeID="ucisMemberOf"> <resolver:Dependency ref="directory"/> <resolver:AttributeEncoder xsi:type="SAML2String" xmlns="urn:mace:shibboleth:2.0:attribute:encoder" name="urn:oid:1.3.6.1.4.1.1466.115.121.1.15" friendlyName="eduPersonAssurance"/> <ValueMap> <ReturnValue>http://id.incommon.org/assurance/silver</ReturnValue> <SourceValue>uc:reference:account:assurance:silver:authorized</SourceValue> </ValueMap> <ValueMap> <ReturnValue>http://uchicago.edu/duo</ReturnValue> <SourceValue>uc:applications:shibboleth:mcb:duo-eligible</SourceValue> </ValueMap> <ValueMap> <ReturnValue>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</ReturnValue> <SourceValue>uc:applications:shibboleth:mcb:password</SourceValue> </ValueMap> </resolver:AttributeDefinition>
Configure in
idp.properties
the following for AuthN flows:# Regular expression matching login flows to enable, e.g. IPAddress|Password idp.authn.flows=Password|Duo # Regular expression of forced "initial" methods when no session exists, # usually in conjunction with the idp.authn.resolveAttribute property below. idp.authn.flows.initial = Password # Set to an attribute ID to resolve prior to selecting authentication flows; # its values are used to filter the flows to allow. idp.authn.resolveAttribute = eduPersonAssurance
In order to prevent bypassing of MFA during forceAuthn caused by a principal switch (another user logs in during the force prompt who has MFA where the first user didn't have MFA), you need to further adjust
idp.properties
and setidp.authn.identitySwitchIsError
to true.Setup your
authn-comparison.xml
to replicate the MCB functionality of defining for the IdP which contexts will satisfy which other contexts. In this example Duo and Silver are ok for Password and only duo is equivalent for PasswordProtectedTransport:<bean id="shibboleth.BetterClassRefMatchFactory" parent="shibboleth.InexactMatchFactory"> <property name="matchingRules"> <map> <entry key="urn:oasis:names:tc:SAML:2.0:ac:classes:Password"> <list> <value>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</value> <value>http://uchicago.edu/duo</value> <value>http://id.incommon.org/assurance/silver</value> </list> </entry> <entry key="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"> <list> <value>http://uchicago.edu/duo</value> <!-- remove silver from PPT equivalence as it causes silvered users who have opted into 2FA forcing to not have 2FA because silver is just as good as Duo for PPT --> <!-- <value>http://id.incommon.org/assurance/silver</value> --> </list> </entry> </map> </property> </bean>
In
conf/intercept/context-check-intercept-config.xml
define further the relationship between PasswordProtectedTransport & Duo/Silver (which are both initially handled by Password flow). What you're doing here is causing the IdP to ensure that if the SP requests the Silver context that the user was able to meet the context via the eduPersonAssurance lookup. If that condition cannot be met, then fail.<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd" default-init-method="initialize" default-destroy-method="destroy"> <!-- Condition to evaluate to interrupt SSO flows to check the state of the transaction before allowing. Typically the flow itself will be activated based on configuration in relying-party.xml, and this controls whether to proceed if the flow is activated. The most common use for this flow is to check the set of resolved/filtered attributes and values to see if the user is authorized or provisioned into a service. --> <bean id="shibboleth.context-check.Condition" parent="shibboleth.Conditions.AND"> <constructor-arg> <list> <bean class="net.shibboleth.idp.profile.logic.AuthnClassPredicate" c:authnClassesToMatch-ref="authnClassesToMatch" c:authnClassesToForgive-ref="authnClassesToForgive" c:predicateToDelegate-ref="attributePredicate" /> </list> </constructor-arg> </bean> <util:set id="authnClassesToMatch"> <value>http://id.incommon.org/assurance/silver</value> <value>http://uchicago.edu/duo</value> </util:set> <util:set id="authnClassesToForgive"> <value>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</value> </util:set> <bean id="attributePredicate" class="net.shibboleth.idp.profile.logic.SimpleAttributePredicate"> <property name="attributeValueMap"> <map> <entry key="eduPersonAssurance"> <list> <value>http://id.incommon.org/assurance/silver</value> <value>http://uchicago.edu/duo</value> </list> </entry> </map> </property> </bean> </beans>
You should now have an IdP that acts like it did under the v2 MCB assuming you used Password initial authN and user-opt-in to Duo.One note about this config. If an SP explicitly requests PasswordProtectedTransport, and the user is trying to force all authN to require Duo (by not allowing PPT in the eduPersonAssurance attribute) the user will not be prompted for Duo. The Duo forcing only happens if the AuthnContextClass was either explicitly unspecified or not sent in the AuthnRequest.
Related articles
Filter by label
There are no items with the selected labels at this time.
Alternative Implementation
University of Chicago uses a Duo plugin developed by Unicon. Later, Duo Security Inc released their own Shibboleth v3 plugin; see Contributions and Extensions. Duo's plugin cannot (easily) support the full MCB functionality described on this page, but it might have some other advantages. If you're shopping around, look at both.