Complicating my MFA implementation

Wessel, Keith kwessel at
Tue Apr 26 11:37:32 EDT 2016

Thanks, Dave. But I’m missing something here. I understand, based on the docs on the wiki, how to trigger the Duo flow based on requested authn context. I also understand Mike’s configuration, or at least I thought I did. But if I used that to define eduPersonAssurance along-side the config from the wiki, it would only be used if the requested context was for a 2FA, wouldn’t it? I’m unclear how the opt-in-for-everything (case #1) would ever be used.


From: David Langenberg [mailto:davel at] 
Sent: Monday, April 25, 2016 9:31 PM
To: Wessel, Keith <kwessel at>; Shib Users <users at>
Subject: RE: Complicating my MFA implementation

With Mike's / my setup you'll also get the option for the SP to request 2FA by context.  As for what gets returned, if the SP requests PPT, then PPT gets returned even if the IdP performed Duo (because the authnContextComparison stuff makes Duo == Password).  If the SP goes for unspecified (or doesn't specify) then the Duo context gets returned.  If the SP explicitly requests Duo, then it's Duo.


David Langenberg
Identity & Access Management Architect
University of Chicago

On April 25, 2016 at 7:48:22 PM, Wessel, Keith (kwessel at wrote:
Very interesting, Mike. Thanks for sharing. I assume the rest of your configuration (outside the attribute resolver config) handles step-up authentication for a non-opt-in user who logged into a regular service at the start of their session then visited a service requiring the 2nd factor. Is that correct? 

What context gets returned for an opt-in user who logs into any old SP, meaning one that doesn't require MFA? Does the password context get returned if that's what they request? Or do you always return the Duo context if Duo was completed, regardless of request? 

Finally, this setup assumes that the list of SPs requiring Duo is controlled by the IDP. Is there any way to make it leverage the requested context instead, allowing MFA to be requested by the SP in the SAML request instead of making it all depend on IDP config and grou configuration? 


-----Original Message----- 
From: users [mailto:users-bounces at] On Behalf Of Michael A Grady 
Sent: Monday, April 25, 2016 8:33 PM 
To: Shib Users <users at> 
Subject: Re: Complicating my MFA implementation 

> On Apr 25, 2016, at 3:12 PM, Wessel, Keith <kwessel at> wrote: 
> The config I'm referring to is in step 8 of this page: 
> With that in mind, is that a valid use of the intercept? Or is that a bad idea? 
> And is there any way, if it's not a bad idea, to craft it into what I was talking about with opt-in? If not, I'll look into the custom subflow. 
> Thanks, 
> Keith 

That is somewhat specific to U Chicago, where they have folks that qualify for Silver based on password alone. Nothing to do with whether Duo was triggered. 

You can get quite complicated with your logic for calculating eduPersonAssurance. Not that many would want to get as complicated as the below, but I include it as an example of what you could do if you so chose to do it. You can consider the SP entityID, the person, and the group memberships amongst other things. 

One place we were working with was considering managing groups based on the SP entityID, where the users who had to do MFA for that SP would be identified in some field in that group. That's likely more manageable than below, if you are going to have a limited set of users required to do Duo for a given SP. 

So here is an example of "beyond complex" logic, one way of implementing the use cases (below) that I was asked to satisfy within the resolver. (You could just put all the logic into the script, and loop thru the memberOf values in the script. Might want to do that if you had lots of groups, and you were looking for groups that matched the "current SP entityID".) 

You'll see this gets pretty complex. Partly because I'm allowing the possibility that the sets of SPs for each of the cases could be different sets of SPs, and partly because I'm allowing for multiple opt-in type groups, one that is opt-in to all, one that is opt-in to Case 4, and one that is Opt-out of Case 3 (i/.e. Case 5). If you don't have that, you can simplify significantly from below. 

Note I handle the group memberships (isMember) in the "dependencyOnly" Mapped attribute 'groupAssuranceSignals'. Then I use those to initialize a hash in the script, and reference those values at the needed places. And the initialization of each set of SPs happens where it is needed, and done in a way I thought would be easy to maintain -- as a "hash". So you'd need to plug in the right group names, the right SP entityIDs, etc. 

As info, here are the use cases you presented to be "solved". But I allowed for opt-in to potentially be more than one group, and the sets of SPs to be different. If you don't want that, some stuff could be removed, or less "sets of SP entityIDs" initialized. 

1. Require Duo login for opt-in users [i] logging into the IdP (i.e., any SP). 
2. Require Duo login for all users logging into specific SPs. 
3. Require Duo login only for a certain user population [ii] logging into specific SPs. 
4. Require Duo login only for opt-in users [i] when logging into specific SPs. 
5. Opt-out [i] certain users from Use Case #3. 


<resolver:AttributeDefinition xsi:type="ad:Mapped" id="groupAssuranceSignals" sourceAttributeID="memberOf" dependencyOnly="true"> 
<resolver:Dependency ref="myLDAP"/> 
<ad:ReturnValue>use case 1 users opt-in to all SPs</ad:ReturnValue> 
<ad:SourceValue ignoreCase="true">CN=Duo Opt-in,OU=Groups,OU=CAMPUS,DC=campustest,DC=net</ad:SourceValue> 
<ad:ReturnValue>use case 4 users for specific SPs</ad:ReturnValue> 
<ad:SourceValue ignoreCase="true">CN=Duo For Use Case 4,OU=Groups,OU=CAMPUS,DC=campustest,DC=net</ad:SourceValue> 
<ad:ReturnValue>exempt users from use case 3</ad:ReturnValue> 
<ad:SourceValue ignoreCase="true">CN=Duo Exempt For Use Case 3,OU=Groups,OU=CAMPUS,DC=campustest,DC=net</ad:SourceValue> 

<resolver:AttributeDefinition id="eduPersonAssurance" xsi:type="ad:Script"> 
<resolver:Dependency ref="myLDAP" /> 
<resolver:Dependency ref="groupAssuranceSignals" /> 
<resolver:Dependency ref="eduPersonAffiliation" /> 
<resolver:AttributeEncoder xsi:type="enc:SAML2String" name="urn:oid:" friendlyName="eduPersonAssurance" encodeType="false" /> 

logger = Java.type("org.slf4j.LoggerFactory").getLogger("net.shibboleth.idp.attribute.resolver.buildEduPersonAssurance"); 
var duoAuthnContext = ''; // set the context value we are using to indicate Duo 
var spEntityId = resolutionContext.getAttributeRecipientID(); // This SP's entityID 
var contextSet = false; 
eduPersonAssurance.getValues().clear(); //remove any current values, shouldn't be any 

// Set up a "hash" based on any applicable Group memberships this person has 
var groupSignals = new Object(); 
if (typeof groupAssuranceSignals != "undefined" && groupAssuranceSignals.getValues().size() > 0) { 
logger.debug("Found some groupAssuranceSignals"); 
for ( i = 0; i < groupAssuranceSignals.getValues().size(); i++ ) { 
value = groupAssuranceSignals.getValues().get(i); 
groupSignals[ value ] = 1; // create a hash with all the 'group signals' based on isMember 
logger.debug("Group signal set: " + value); 

// Case 1: Require Duo login for opt-in users logging into the IdP (i.e., any SP). 
if ( groupSignals['use case 1 users opt-in to all SPs'] ) { 
contextSet = true; 
logger.debug("Case 1 applied"); 

// Case 2: Require Duo login for all users logging into specific SPs. 
if ( !contextSet ) { // Case 2: see if it applies 
// Initialize Case 2 SPs that require Duo for all users. 
// You only need to do that if the SP's requested authn context needs overriding, 
// otherwise you can depend on that being enforced by requested authn context. 
var SPsAllMustDoDuoFor = new Object(); 
SPsAllMustDoDuoFor['SP 1 entityID'] = 1; 
SPsAllMustDoDuoFor['SP 2 entityID'] = 1; 

if (SPsAllMustDoDuoFor[spEntityId]) { // check the current SP against Case 2 SPs 
eduPersonAssurance.getValues().add(duoAuthnContext); // Yes, case 2 
contextSet = true; 
logger.debug("Case 2 applied"); 

// Case 4: Require Duo login only for opt-in users when logging into specific SPs. 
if ( !contextSet ) { // Case 4: see if it applies 
// Initialize Case 4 SPs that require Duo for opt-in users 
var SPsForCase4 = new Object(); 
SPsForCase4['SP 1 entityID'] = 1; 
SPsForCase4['SP 2 entityID'] = 1; 
// check the current SP against Case 4 SPs and User for Case 4 opt-in 
if (SPsForCase4[spEntityId] && groupSignals['use case 4 users for specific SPs'] ) { 
eduPersonAssurance.getValues().add(duoAuthnContext); // Yes, case 4 
contextSet = true; 
logger.debug("Case 4 applied"); 

// Case 3: Require Duo login only for a certain user population logging into specific SPs 
// Case 5: Opt-out certain users from Use Case #3 
if ( !contextSet ) { // Left with Cases 3 & 5 to check 
if (groupSignals['exempt users from use case 3']) { // Case 5 overrides Case 3 
// Case 5 applies, these users are exempt from Case 3, allow them PPT 
contextSet = true; 
logger.debug("Case 5 applied"); 
} else { // Case 3: see if it applies 
// Initialize Case 3 SPs that require Duo for all users with certain affiliation 
var SPsForCase3 = new Object(); 
SPsForCase3['SP 1 entityID'] = 1; 
SPsForCase3['SP 2 entityID'] = 1; 

// See if case 3 applies to this SP, and if so, if user 
// has eduPersonAffiliation requiring Duo. 
if (SPsForCase3[spEntityId] && typeof eduPersonAffiliation != "undefined" && eduPersonAffiliation.getValues().size() > 0) { 
for ( i = 0; i < eduPersonAffiliation.getValues().size(); i++ ) { 
value = eduPersonAffiliation.getValues().get(i); 
if (value == "employee") { 
contextSet = true; 
logger.debug("Case 3 applied"); 

// if no eduPersonAssurance value has been set yet, set it to PPT 
if (eduPersonAssurance.getValues().size() == 0) { // Or you could just check for !contextSet 
logger.debug("No Cases applied, setting default value"); 

logger.debug("eduPersonAssurance final value: " + eduPersonAssurance.getValues().get(0)); 


Michael A. Grady 
IAM Architect, Unicon, Inc. 

To unsubscribe from this list send an email to users-unsubscribe at 

More information about the users mailing list