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 in older versions of the IdP, 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.
Overview
This authentication plugin (DuoOIDC) supports Duo’s strong two-factor authentication using their OIDC-based integration model introduced in 2021 (Duo OIDC AuthAPI). 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.
Plugin Installation
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.
Dependencies
This module depends on the Shibboleth OIDCCommon plugin which you must install first. The installer should prevent installation if this is not in place.
Starting with IdP 4.2 you can install the latest plugin version supported on your IdP version with .\plugin.sh -I <PluginId>
Plugin
Plugin ID
Module(s)
Authentication Flow ID
Bug Reporting
Plugin
Plugin ID
Module(s)
Authentication Flow ID
Bug Reporting
Duo Universal Prompt via the Shibboleth Nimbus Client
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
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)
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 installations upgraded from versions prior 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 easiest 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.
General Configuration
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 client ID, API hostname and client secret into the conf/authn/duo-oidc.properties file to form your Duo Integration. The client ID and secret will likely appear as integration key and secret key until you make your first request using the new AuthAPI i.e. actually use this plugin for authentication. Note, you may want to keep the client secret in credentials/secrets.properties for consistency with other IdP secrets
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:
Where <hostname> and <port> match that of your running IdP - the port can be omitted if your IdP uses the standard HTTP/HTTPS port e.g. 80 and 443 respectively. You are free to change part of that path i.e. Authn/Duo/2FA by setting the idp.duo.oidc.externalAuthnPath property. Although in most cases there is little reason for doing so. The endpoint itself needs only be accessible by the end-user’s user-agent (browser).
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:
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 checkSecondFactor script if required, see the MFA page for a full discussion of the possibilities.
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 supportedPrincipals collection compatible with this type of authentication mechanism alongside any other factor(s) used. In the SAML 2.0 world (and nowadays more generally, e.g., OpenID Connect) these are specified as an Authentication Context Class.
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 supportedPrincipal configurations, see below.
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:
X-Shibboleth-Duo-Factor
X-Shibboleth-Duo-Device
X-Shibboleth-Duo-Passcode
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.