Page tree
Skip to end of metadata
Go to start of metadata

Current File(s): conf/authn/duo-authn-config.xml, conf/authn/duo.properties, views/duo.vm

Format: Native Spring

Overview

This feature has been implemented by the Shibboleth Project team using libraries from Duo Security but is not the same as the previously available Duo login flows available from either Duo Security or Unicon. While those implementations remain available and should continue to work, this implementation is the only one supported by the project itself.

The authn/Duo login flow is a native implementation of the Duo Security product's DuoWeb authentication interface that leverages an embedded IFRAME. Duo is a popular commercial solution for adding additional authentication factors to existing credentials. It is designed to be used in conjunction with an existing factor, usually a password. It is therefore implemented in a manner that assumes an earlier authentication step has already been completed, and is designed to be used in conjunction with the MultiFactor login flow as part of a combined workflow.

The result of this flow is a Java Subject containing a DuoPrincipal. Note that no actual "username" is produced; it is assumed that one or more other login flows will contribute content to the Subject suitable for an existing subject canonicalization flow to operate on the aggregate result. All this means is that it assumes the Password login flow (or some other traditional mechanism) is used as part of a MultiFactor workflow, and the result will normally be correct.

General Configuration

For a typical integration, configuration of the Duo flow itself is very simple: just fill in your Duo integration settings in conf/authn/duo.properties. This assumes you have a single Duo integration, which is true for most sites. There are a couple of more advanced scenarios that can be accommodated, and details are included below.

The other important step is to define something to represent this mechanism to the outside "SAML" world as an authentication context class by adjusting the supportedPrincipals property on the Duo (and probably MFA) flow descriptors in conf/authn/general-authn.xml. There is no "standard" context class (or SAML 1 authentication method) to represent most forms of MFA, and moreover, experience has shown that it's a bad idea to create a strong coupling between applications and the exact technologies that you use for authentication.

As a result, the default configuration contains only a placeholder value to use that you will need to change, but there is currently no standard value to use. One possible choice to consider is a profile under development by InCommon, but your particular deployment may or may not satisfy its requirements. Regardless of specifics, the approach is a good one in general: a generic URI representing the use of MFA.

Whatever value used, in most cases you can simply add it to the flow descriptor(s) where needed, and the Duo flow will automatically contribute that value to the results it builds.

User Interface

The Duo flow relies on an embedded IFRAME that points directly to your Duo API host, and a Velocity template (views/duo.vm) is provided that contains the necessary HTML, around which you're free to customize. All of the necessary JavaScript is included with the IdP in a fashion that allows us to update it underneath the template when Duo provides updated versions.

Advanced Configuration

Multiple Duo Integrations

In the event that you need to support multiple sets of Duo integration parameters, you can implement a Function<ProfileRequestContext,DuoIntegration> in Java or a script in a bean named shibboleth.authn.Duo.DuoIntegrationStrategy, which can be defined in conf/authn/duo-authn-config.xml.

As an example, let's say you want to create a table that maps certain services to a particular integration, and uses a separate default for everything else. You can implement this with a simple map and a script that operates on it.

Multiple Duo Integrations
<bean id="DefaultDuo" class="net.shibboleth.idp.authn.duo.BasicDuoIntegration"
    p:APIHost="%{idp.duo.apiHost:none}"
    p:applicationKey="%{idp.duo.applicationKey:none}"
    p:integrationKey="%{idp.duo.integrationKey:none}"
    p:secretKey="%{idp.duo.secretKey:none}" />

<bean id="SpecialDuo" class="net.shibboleth.idp.authn.duo.BasicDuoIntegration"
    p:APIHost="%{idp.specialduo.apiHost:none}"
    p:applicationKey="%{idp.specialduo.applicationKey:none}"
    p:integrationKey="%{idp.specialduo.integrationKey:none}"
    p:secretKey="%{idp.specialduo.secretKey:none}" />

<util:map id="DuoIntegrationMap">
	<entry key="default" value-ref="DefaultDuo" />
	<entry key="https://special1.example.org/shibboleth" value-ref="SpecialDuo" />
	<entry key="https://special2.example.org/shibboleth" value-ref="SpecialDuo" />
</util:map>

<bean id="shibboleth.authn.Duo.DuoIntegrationStrategy" parent="shibboleth.ContextFunctions.Scripted"
		factory-method="inlineScript"
        p:customObject-ref="DuoIntegrationMap">
	<constructor-arg>
		<value>
		<![CDATA[
		duo = null;
		rpCtx = input.getSubcontext("net.shibboleth.idp.profile.context.RelyingPartyContext");
		if (rpCtx) {
			duo = custom.get(rpCtx.getRelyingPartyId());
		}
		if (duo == null) {
			duo = custom.get("default");
		}
		duo;
		]]>
		</value>
	</constructor-arg>
</bean>

Integration-Specific Principal Sets

In conjunction with the above feature, you can also cause the resulting Java Subject to carry Principals specific to a given Duo integration, which is useful if you want to segregate the integrations during SSO. That is, given two integrations A and B, you may want the use of A to satisfy a request that would use B, but you may not want that.

By default, since the Principals added into the result come from the underlying login flow descriptor for "authn/Duo", a request that uses either integration will produce a result that will be usable on any later request for Duo authentication. To prevent this, you need to do a couple of things:

  • Create a bean in authn/duo-authn-config.xml named shibboleth.authn.Duo.addDefaultPrincipals set to Boolean.FALSE
  • Define separate Principal collections unique to each integration and associate them with the Duo integrations you define

Building on the previous example, the following shows how this might look. The approach is different in that the choice of Duo integration to use is implemented by evaluating each one to pick the first one that satisfies the request. This works in conjunction with triggering the integration to use based on SPs requesting the AuthnContextClassRef defined below (or setting it in a relying party override).

Multiple Integrations with Distinct Principal Sets
<!-- Turn off default behavior in favor of integration-specific principals below. -->
<util:constant id="shibboleth.authn.Duo.addDefaultPrincipals" static-field="java.lang.Boolean.FALSE" />

<bean id="DefaultDuo" class="net.shibboleth.idp.authn.duo.BasicDuoIntegration"
		p:APIHost="%{idp.duo.apiHost:none}"
		p:applicationKey="%{idp.duo.applicationKey:none}"
		p:integrationKey="%{idp.duo.integrationKey:none}"
		p:secretKey="%{idp.duo.secretKey:none}">
	<property name="supportedPrincipals">
		<list>
			<bean parent="shibboleth.SAML2AuthnContextClassRef"
				c:classRef="http://example.org/ac/classes/mfa/default" />
			<bean parent="shibboleth.SAML1AuthenticationMethod"
				c:method="http://example.org/ac/classes/mfa/default" />
		</list>
	</property>
</bean>

<bean id="SpecialDuo" class="net.shibboleth.idp.authn.duo.BasicDuoIntegration"
		p:APIHost="%{idp.specialduo.apiHost:none}"
		p:applicationKey="%{idp.specialduo.applicationKey:none}"
		p:integrationKey="%{idp.specialduo.integrationKey:none}"
		p:secretKey="%{idp.specialduo.secretKey:none}">
	<property name="supportedPrincipals">
		<list>
			<bean parent="shibboleth.SAML2AuthnContextClassRef"
				c:classRef="http://example.org/ac/classes/mfa/special" />
			<bean parent="shibboleth.SAML1AuthenticationMethod"
				c:method="http://example.org/ac/classes/mfa/special" />
		</list>
	</property>
</bean>

<util:list id="DuoIntegrationList">
	<ref bean="SpecialDuo" />
	<ref bean="DefaultDuo" />
</util:list>

<bean id="shibboleth.authn.Duo.DuoIntegrationStrategy" parent="shibboleth.ContextFunctions.Scripted"
		factory-method="inlineScript"
        p:customObject-ref="DuoIntegrationList">
	<constructor-arg>
		<value>
		<![CDATA[
		duo = null;
		authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext");
		iter = custom.iterator();
		while (duo == null && iter.hasNext()) {
			duo = iter.next();
			if (!authCtx.isAcceptable(duo)) {
				duo = null;
			}
		}
		duo;
		]]>
		</value>
	</constructor-arg>
</bean>

Username Determination

By default, the Duo flow is designed to operate with a username derived from one of:

  • a pre-existing session
  • a previously executed login flow

Configuring it to run after a "first factor" flow will automatically satisfy this requirement, and allows you to supply a canonical username from a previous method into the Duo API, which is typically the best approach.

If you need a more flexible approach, you can configure a Function<ProfileRequestContext,String> in a bean named shibboleth.authn.Duo.UsernameLookupStrategy, which can be defined in conf/authn/duo-authn-config.xml.

Reference

Beans

The possible beans expected in authn/duo-authn-config.xml follow:

Bean IDTypeDefaultFunction

shibboleth.authn.Duo.DuoIntegration

DuoIntegrationDerived from settings in duo.properties

Defines a single/static integration with Duo, you can override this bean to supply a non-property-configured alternative

shibboleth.authn.Duo.DuoIntegrationStrategy

Function<ProfileRequestContext,DuoIntegration>
Optional bean to supply the Duo integration settings dynamically
shibboleth.authn.Duo.UsernameLookupStrategyFunction<ProfileRequestContext,String>CanonicalUsernameLookupStrategyOptional bean to supply username
shibboleth.authn.Duo.resultCachingPredicate

Predicate<ProfileRequestContext>


An optional bean that can be defined to control whether to preserve the authentication result in an IdP session
shibboleth.authn.Duo.addDefaultPrincipalsBooleantrueWhether to add the content of the supportedPrincipals property of the underlying flow descriptor to the resulting Subject

Properties

The properties defined in conf/authn/duo.properties follow:

NameTypeFunction
idp.duo.apiHostHostnameDuo API hostname assigned to the integration
idp.duo.applicationKeyString

a secret supplied by you and not shared with Duo; see https://duo.com/docs/duoweb, "Generate an akey".

idp.duo.integrationKeyStringDuo integration key (supplied by Duo)
idp.duo.secretKeyStringDuo secret key (supplied by Duo)

The Duo provided properties will appear in the Duo administrative console:

V2 Compatibility

There is no equivalent V2 feature, apart from third party extensions.

Notes

Note that this flow is configured by default without support for non-browser profiles (namely ECP) because it relies on an IFRAME for authentication. It is hoped that a future version of this flow, or an alternative, may be made available to integrate directly with Duo's native API.

  • No labels