This plugin does not depend on the use of the original Duo login flow and enabling that feature/module is not required in order to install and use this plugin. They can even theoretically co-exist, but this hasn't been extensively tested. |
If you're looking for a quick reference guide that assumes a basic, default, configuration, see here. Note, you really should read this page first.
This authentication plugin (DuoOIDC) supports Duo’s strong two-factor authentication using their OIDC-based integration model introduced in 2021 (Duo OIDC Auth API). This includes both the traditional prompt and the new Universal Prompt. The Universal Prompt is a major UX redesign of the older in-page iFrame prompt. In both cases, the user is redirected, via a full-frame redirect, to a Duo-hosted site using the OIDC protocol to perform second-factor authentication, and the results are made available to the IdP as a form of an OIDC ID Token. Duo's support is compliant with OIDC with a few caveats.
Like the original integration based on their WebSDK V2, this plug-in is designed to be used as a second factor of authentication, so is therefore used in conjunction with an existing ‘first-factor', usually orchestrated by the MFA login flow (see MultiFactorAuthnConfiguration).
By default, the first-factor must produce an “official” username as part of post-login canonicalization which the DuoOIDC flow can use as the Duo username in the second-factor authentication request. In unusual cases it is possible to customize the source of the username.
The result of this flow is a Java Subject containing a DuoPrincipal as well as a custom set of additional Principals, typically representing SAML AuthenticationContextClassRefs.
There are two different DuoOIDC Auth API plugin implementations. Both share the majority of their codebase, the difference being how they interact with Duo’s OIDC Provider. One is based on the official Duo WebSDK v4, and one is based on a Shibboleth implementation using Nimbus’s JOSE-JWT handling - although it is worth noting that part of the common codebase uses the Nimbus library for certain tasks irrespective of which plugin you use.
In most cases, we would suggest trying the Nimbus-based plugin first, particuarly if you plan to make use of the OIDC OP plugin as well, as this avoids a number of duplicated code libraries in the IdP. Duo built their SDK on top of a different OIDC/JOSE library stack, whereas we used Nimbus, allowing more code to be shared across the different components.
DependenciesThis module depends on the Shibboleth OIDCCommon plugin which you must install first. The installer should prevent installation if this is not in place. |
Plugin | Plugin ID | Module ID | Authentication Flow ID | Latest Version |
---|---|---|---|---|
Duo Universal Prompt via the Shibboleth Nimbus Client | net.shibboleth.idp.plugin.authn.duo.nimbus | idp.authn.DuoOIDC | authn/DuoOIDC | 1.2.0: download |
Duo Universal Prompt via Duo WebSDK v4 Client | net.shibboleth.idp.plugin.authn.duo.sdk | idp.authn.DuoOIDC | authn/DuoOIDC | 1.2.0: download |
The following table highlights the differences in their technical specification to help you decide which to install. Note, their functional specification (how it works for the end-user) is the same for either.
Feature | Duo WebSDK v4 | Shibboleth Nimbus |
---|---|---|
Based on the official SDK | X | |
Duo Endpoint and Configuration Health Check | X | X |
Duo 2FA result token signature (HMAC) checking | X | X |
Duo 2FA result token encryption handling (not provided by Duo) | ||
Duo 2FA result token claims verification | X | X |
Duo 2FA result token nonce verification | X | |
Customizable HttpClient implementation | X | |
Customizable TrustEngine implementation | X | |
HTTP Public Key Pinning | X | X |
Supports TLS Certification Revocation Checking | X | X |
Customisable JSON response mapper | X |
For a detailed guide on configuring modules, see the ModuleConfiguration topic. Once the plugin has been installed, its module should be enabled automatically for you:
/%{idp.home}/bin$ ./module.sh -l ... Module: idp.authn.DuoOIDC [ENABLED] |
However, if you need to enable it you can using the module
command:
/%{idp.home}/bin$ ./module.sh -e idp.authn.DuoOIDC |
Either manual or automatic module enablement will copy across the following configuration files from the jar:
jar:duo-oidc-authn-config.xml -> conf/authn/duo-oidc-authn-config.xml jar:duo-oidc.properties -> conf/authn/duo-oidc.properties |
Automatic Flow RegistrationThe flow definition, default beans, and authentication flow descriptor are loaded automatically from well-known location(s) from the plugin’s classpath. The default behavior configured in those files can be overridden via the two configuration files shown above. |
Systems upgraded to V4.1 are also likely to require adding the idp.searchForProperties=true property to their idp.properties file, or else an explicit reference would have to be added to the new property file added by the module. It's best to clean up the property situation prior to using plugins that add their own.
Once installed and enabled, you will then need to start configuring the flow.
Once you have configured a Shibboleth ‘Protected Application’ and enabled support for the Universal Prompt in the Duo Admin Panel (see also Duo Universal Prompt), you'll need to copy across your Next, you need to specify a redirection URI as per the OAuth2.0 specification (RFC6749). This is the endpoint the Duo Universal Prompt will redirect the end-users user-agent (browser) to after successful second-factor authentication. By default, Duo does not require you to pre-register redirect URIs (you can request this if desired), instead taking one supplied by the IdP inside a signed JWT request object. This opens a few different possibilities depending on which of the two clients you choose. The simplest, supported by both clients, is to define a static idp.duo.oidc.redirectURL property in the conf/authn/duo-oidc.properties file: Redirect URI
Where Alternatively, you can let the plugin determine the redirect URI from the Host header sent from the client that issues the 'first' Duo 2FA request. To do so, comment out the idp.duo.oidc.redirectURL property and then, in order to prevent HTTP Host header injection attacks (and possibly leaking your authorization code to a malicious actor), declare one or more comma-seperated allowed 'origins' [RFC6454] in the property idp.duo.oidc.redirecturl.allowedOrigins. Each origin is a combination of scheme, host, and port. For schemes where the default port is used e.g. HTTPS on port 443, the port can be omitted. For example, assuming a production IdP has a hostname of prod.example.com using the HTTPS scheme over the default port, and a matching development IdP has a hostname of dev.example.com running on a custom port 8443, the following origins would be sufficient: Allowed Origins
This can be useful if you want to keep a single generic configuration between development, staging, and production servers, etc. Further to this, if you are using the Shibboleth Nimbus client, the redirect URI will be created dynamically from the Host header per-request - there is no extra configuration for this, it works per-request by default. This could be useful, for example, if a single IdP instance is serving requests from more than one virtual-host, and each Duo 2FA request will need to be redirected back to the originating Hostname in order to successfully complete the request. As an advanced configuration option, you can specify more than one Duo integration and use a runtime function to determine which is used per authentication request. Given that Duo's 'second-factor' authentication runs after a 'first-factor' authentication method, you will also need to enable the "idp.authn.MFA" module in addition to an appropriate first-factor (e.g., the Password flow module, "idp.authn.Password"), and typically will need to configure the MFA flow to make use of this one as a second factor. |
The flow supports a secondary integration allowing use of the Duo AuthAPI for non-browser use cases such as SAML ECP. To enable support for the AuthAPI, you will need to define an additional integration with Duo. A second set of properties is defined to allow this. Also, the default settings for this login flow mark it as not supporting non-browser use. This needs to be adjusted by setting the idp.authn.DuoOIDC.nonBrowserSupported property. By default, a built-in HttpClient bean is used to communicate with the Duo AuthAPI with fairly vanilla TLS behavior that relies on the system defaults. It's possible to customize this heavily using a pair of beans. More advanced documentation is in the HttpClientConfiguration topic. |
Here we describe an example MFA flow using both the MFA and Password flows (in addition to the new DuoOIDC flow). Of course, this example assumes you have also enabled those modules in addition to this plugin/module, as per their documentation pages. For testing, the credential store of the Password flow can be configured from a simple flat file of usernames/passwords, for example, using the HTPasswdCredentialValidator in conf/authn/password-authn-config.xml as shown below. If this file is missing, you have not yet enabled the "idp.authn.Password" module. Example password validator
Or, you may already have configured a 'production' ready password validator e.g. using LDAP. Either way, your next step is to compose both these flows into a suitable multi-factor authentication flow. An example flow defined in conf/authn/mfa-authn-config.xml file is shown below. If this file is missing, you have not yet enabled the "idp.authn.MFA" module. Example MFA configuration
In summary, the IdP will run the "authn/Password" flow followed by the "authn/DuoOIDC" flow. More complex business/orchestration logic can be added to the After configuring the MFA orchestration logic appropriately, you should consider how to represent the multi-factor authentication mechanism to the outside world. |
As the DuoOIDC plugin typically runs after a first-factor authentication method orchestrated by the MFA flow, the MFA flow must present to the system a Those already set by default on the MFA flow are described by the idp.authn.MFA.supportedPrincipals property in the conf/authn/authn.properties file. (If your system has been upgraded from V4.0 or earlier, then you may not have migrated to the property-centric settings V4.1 allows, and may have the supported Principals enumerated in conf/authn/general-authn.xml.) Either way,these need to be adjusted to also include those that are also exposed by the "authn/DuoOIDC" flow. This flow defaults to an example set defined by the idp.authn.DuoOIDC.supportedPrincipals property in conf/duo-oidc.properties. If not set, it will fall back to the definition of the idp.authn.Duo.supportedPrincipals property. As for what these values should be:
For more advanced |
The plugin itself does not provide a native IdP view to customize because all second-factor authentication interactions occur on a Duo-hosted site to which the IdP redirects the user-agent (browser). See the Duo Admin Panel to see what customizations are possible on the Duo site. It is important to note that unsuccessful second-factor authentication terminates on the Duo site. The IdP will not receive notification of the failure, and hence authentication failure cannot be propagated further to the SP. The non-browser variant has no UI and relies on a set of HTTP request headers from the client. Authentication relies on knowing the type of Duo factor to use, the device to use, and occasionally a passcode. Often none are needed and the whole process is automatic (the factor and device are defaulted to "auto"). Specifying a device is generally done using a name the user must associate with the device themselves. Some factors rely on a passcode being supplied. The headers can be changed but default to:
Factor is one of "auto", "push", "phone", or "passcode". The "sms" factor does not work, but will fail while resulting in the issuance of codes via SMS for subsequent use. |
By default, the Duo flow is designed to operate with a username derived from one of:
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 integration, which is typically the best approach. If you need a more flexible approach, you can configure a bean named shibboleth.authn.DuoOIDC.UsernameLookupStrategy of type Function<ProfileRequestContext,String>, which can be defined in conf/authn/duo-oidc-authn-config.xml. The function returns the canonical username to use and can introspect the ProfileRequestContext if required. |
Both plugin variants are, by default, configured with the same static set of ‘pinned’ root certificate authority certificates (trust anchors). As a result, the chain of trust associated with the end-entity certificate presented by the Duo API must be anchored by one of these root authorities and no other e.g. not the default set provided by the JDK. The current set of pinned root certificates are listed in the table below:
The Duo WebSDK v4 plugin uses public-key hash pinning, whereas the Shibboleth Nimbus plugin pins the entire CA certificate. The set of pinned hashes/certificates can be overridden for each plugin by including one of the following beans in the conf/authn/duo-oidc-authn-config.xml file: For the Shibboleth Nimbus plugin - the values in the list are any valid Spring Resource: Override Shibboleth Nimbus Pinned Trusted Certificates
For the Duo WebSDK v4 plugin - the values in the list are the public key hashes of pinned certificates: Override Duo WebSDK v4 Pinned Trusted Certificates
|
For a basic configuration, you set the
Both are described below. Directly on the DuoOIDCIntegration You can define Principal sets per Duo Integration. This is particularly useful if you have more than one integration, and want each to satisfy a different Authentication Context Class Reference requested by a Service Provider. In the examples, the bean properties set are purely examples, they could be set inline or rely on property names of your own choice. Per Integration Supported Principals
Context to Principal Mapping Strategy If you want to add Principals to the Java Subject based on runtime information received inside a Duo result (authentication) token, you can add a Java bean named shibboleth.authn.DuoOIDC.ContextToPrincipalMappingStrategy that could, for example, inspect the JWT ClaimsSet of the Duo token. The type of this bean is Function<ProfileRequestContext,Collection<Principal>> (input is the profile request context tree, and the output is the collection to inject into the Subject). The following example script adds the ACR "http://example.org/ac/classes/mfa/strong" to the Java Subject if the 'duo_push' two-factor authentication method was used (this is not a commentary on the "strength" of push-based MFA, strictly an example). Example Context To Principal Mapping Strategy
It is important to note that if the mapping strategy bean is defined, the default The following JSON listing shows an example of the current Duo JWT ClaimsSet for reference when constructing a mapping strategy. Refer to Duo's documentation as the final word on the subject. Duo JWT ClaimsSet
|
As is common for OIDC Providers, Duo presents an HTTP API for the IdP to communicate with directly as a callback. With the Shibboleth Nimbus plugin, you have the option to override the default HttpClient object used during that communication by specifying your own HttpClientFactoryBean bean named shibboleth.authn.DuoOIDC.nimbus.HttpClient. It is then up to you what 'security' features you add to this client e.g. certificate trust, alongside standard connection properties such as connection timeouts. The default client already contains enough sensible, overridable, defaults that it would only be in very unusual cases that you would want to override it but is a standard approach to allow for it. |
If you need to support multiple sets of Duo integration parameters, you can implement a Function<ProfileRequestContext,DuoOIDCIntegration> in Java or a script in a bean named shibboleth.authn.DuoOIDC.DuoIntegrationStrategy, which can be defined in conf/authn/duo-oidc-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. In the examples, the bean properties set are purely examples, they could be set inline or rely on property names of your own choice. Multiple Duo Integrations
|
Neither plugin/client is configured by default to check the revocation status of the certificates presented during the TLS handshake. Generally, there are three options for this:
To configure revocation checking with the Duo WebSDK v4 plugin, use the normal Java Trust Manager system properties (see the CertPathDocs). To enable revocation checking with the Shibboleth Nimbus plugin, you will need to set the property idp.duo.oidc.nimbus.checkRevocation to true in the conf/authn/duo-oidc.properties file *and* do one or more of the following:
Note that Duo does not support OSCP stapling, so this is not an option at this stage. |
|
These beans are specific to the Duo WebSDK-based plugin only:
|
These beans are specific to the Nimbus-based plugin only:
|
These beans refer to the Duo Auth API (non-OIDC) which supports non-browser flows.
|
The DuoOIDC-specific properties defined in conf/authn/duo-oidc.properties follow:
|
The below table are properties that only apply to the Shibboleth Nimbus plugin:
|
The general properties configuring this flow via authn/authn.properties are:
As a non-password based flow, the supportedPrincipals property defaults to the following XML:
In property form, this is expressed as:
However, this default is (obviously) intended purely as an illustrative example of how to define your own values, as there are no standard ones to use. |
To replace the internally defined flow descriptor bean, the following XML is required:
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. |