DuoOIDCPasswordless
The project would like to extend appreciation to Steven Premeau, University of Maine System, for suggesting this feature, providing an initial contribution, and working extensively with the project to test and refine it.
This feature requires V2.1.0+ of the DuoOIDC plugin.
Background
The Duo plugin includes optional support for using the Duo service as the sole authentication mechanism in the IdP rather than as a second factor solution. Typically this is useful in order to use Duo to support passwordless authentication using modern approaches like WebAuthn, as in the newly branded āpasskeysā technology integrated into most newer devices.
There are limitations; in particular, the requirement to know (or ask for) the username first. The use of on-device ādiscoverableā credentials without identifying a user ahead of time thus cannot be supported, though they are supported once the user is identified to Duo. In addition, Duo does not presently allow any integration to disable the use of bypass codes (as might be issued by help desk staff), but the plugin can be configured to detect and block their use, or any other unexpected factor, in order to ensure that if the flow completes successfully that only one of a set of expected methods was used.
However, there are substantial advantages to using the existing service for organizations already committed to the Duo service, and the intetgration is relatively simple to enable and configure, and supports fallback to traditional authentication methods. It does some work to prevent ātrappingā a user into attempting a login they cannot complete, though it is impossible to prevent that completely due to Duo lacking a ācancelā feature at present.
The new feature also has been designed with some knowledge of shared machine considerations, and also allows for various kinds of āgradual adoptionā scenarios such as pilot groups and so forth, to manage the introduction of the feature to a large community.
Overview
Ultimately the MFA flow is used to control the use of this new feature and orchestrate the use of the flow in its new form or its traditional mode. Examples of how to configure this are included near the end of this topic, and of course the MFA feature in the IdP can be configured to do many more exotic things in support of this workflow depending on local requirements.
The general behavior modeled (if the feature is enabled and triggered):
After a standard Duo authentication is completed, a condition is evaulated against the request and if true, the subject is given a choice to āopt-inā so that an encrypted cookie containing the username is set for future requests. The plugin will also identify scenarios where an existing cookie should be removed. If the subject opts-out, then this decision is also remembered with a fixed cookie value to avoid constant nagging.
Subsequent requests with the feature triggered will detect the presence of the cookie, and it will (generally) present a new view prior to the handoff to Duo that identifies who it believes the subject to be so the person may indicate how they want to proceed.
If traditional login is preferred, or the subject self-identifies as a different identity, then control returns to the MFA flow with specific events, after which the MFA flow is expected to do what it had been doing originally, whatever that was.
If passwordless login is chosen, the plugin will invoke the Duo service using the desired passwordless integration.
The factor used is checked against a set that is deemed acceptable (configurable of course) and if so, the resulting Java Subject will automatically include a UsernamePrincipal object containing the username passed to Duo, allowing the MFA flow to potentially complete with no additional configuration needed.
In most cases, once āprimedā, the subject is expected to be able to simply press the Return key to trigger authentication on a previously opted-in device.
The flow also supports offering the same āshared machineā and ārevoking consentā options that exist on the default login form, the former impacting the behavior by optionally clearing the opt-in cookie. It is assumed that the passwordless model is not in general a good fit for shared environments, but this is somewhat configurable.
Deployers with more advanced requirements can also āassume responsibilityā for establishing the userās identity and manage the cookie to some degree themselves (via an API), and drive the passwordless behavior more directly, even bypassing the built-in view to go directly into Duo.
Demonstration Videos
A number of videos demonstrating the features in different situations are attached to this topic:
Title | Description | File |
---|---|---|
Standard Username and Password Authentication | The user has not opted into passwordless authentication, and the service provider only requires password authentication. This demonstrates a standard, basic, username and password flow. | |
Opt-In To Passwordless Authentication | The service provider requires multi-factor authentication, the user performs multi-factor with Duo and uses a second factor that is acceptable for passwordless. The user opts-in to passwordless and uses that as the sole factor on the next authentication. | not-opted-in-requires-mfa-uses-correct-factor-and-opts-in.mov |
Unable To Opt-In to Passwordless Authentication | The service provider requires multi-factor authentication, the user performs multi-factor with Duo but uses a second factor that is unacceptable for passwordless. The user can not use passwordless for subsequent authentications. | opted-in-requires-mfa-not-eligble-for-passwordless-wrong-factor.mov |
Opt-In To Passwordless, But Then Use an Unacceptable Factor For Passwordless | The service provider requires multi-factor authentication, the user performs multi-factor with Duo and uses a second factor that is acceptable for passwordless. Opts-in to passwordless. However, for the subsequent passwordless authentication they change to an unacceptable sole factor. The login fails. | |
Already Opted-In To Passwordless, But Chooses The Password Flow | The user has previously opted-in to passwordless authentication but decides to use username and password authentication instead. | |
A Different User Using the Same Browser As A User Who Opted-In | A different user, using the same browser, opted-in to passwordless authentication. The current user recognises it is not them (and their credentials would not work) and uses the āNot Youā link. | |
Administrative Flow for User Control Of Opt-In Status | The user signs into the administrative endpoint to manage their passwordless opt-in status (cookie) | |
Administrative Flow for Admin User Control Of Opt-In Status and Username | A user, with administrative rights, signs into the administrative endpoint to manage both their passwordless opt-in status (cookie) and the username stored inside the cookie. |
Duo Integration Considerations
This is paramount: you almost certainly CANNOT use your existing Duo integration(s) to support this use case, and there are options that one might normally set without much thought that really need a different perspective here. The model here is to leverage Duo to do less than it normally does functionally, and thatās a bit of an odd way of thinking that isnāt natural to everybody.
Until thereās more general guidance about this from Duo, weāll note the following:
You need to very carefully think about the factors you enable in the integration, and that also extends to the Remember Me feature. By default our pluginās behavior is to require the use of a specific type of factor, āPlatform authenticator (2fa)ā, and only that. As a result, even if you enable Remember Me, it wonāt be permitted and so it should generally be turned off, along with all the other factors they support. Eventually they may allow turning off Bypass Codes as well, but that isnāt possible yet.
The question of whether to permit so-called āRoaming authenticator (2fa)ā devices may be ānoā, but Duo supports an option to require user verification for roaming devices. Unfortunately, that flag is something the client asserts, and we know for certain that there are roaming authenticator implementations that signal UV, but do not actually perform it. Itās not clear at this point if that wlll be common or rare, or who would push back on such implementations. They are disallowed by default at present.
In addition, you will need to ensure that the option to allow unrecognized users to enroll themselves has to be disabled as well. Normally Duo assumes a first factor was used to authenticate the username before taking control and here that doesnāt happen. Blocking unknown users outright is what you want here.
Plugin Configuration
The additional files added by this feature are the new view templates, views/passwordless-optin.vm and views/passwordless.vm. The former is presented after second factor usage (conditionally) and gives the subject the option to have a cookie issued to their browser to to allow passwordless authentication as that identity in the future. To be clear, the cookie is not used to authenticate the subject but rather triggers the presentation of the passwordless option and then drives the eventual use of Duo using that username.
Note that it is impractical, though not impossible, for somebody to manipulate the username that ends up being used, but that doesnāt buy an attacker much unless they actually possess and can use WebAuthn credentials for that identity.
A set of new properties is supported to enable this feature and configure a dedicated Duo integration for this use case. Many of the advanced settings will default back to their usual or overridden values for existing Duo integrations. The idp.duo.oidc.passwordless.enabled property is the main toggle to turn on to use the new featurs, though there are other steps to actually make it run described later. Usually only these few need to be provided:
idp.duo.oidc.passwordless.enabled = true
idp.duo.oidc.passwordless.apiHost = yourvalue
idp.duo.oidc.passwordless.clientId = yourvalue
idp.duo.oidc.passwordless.secretKey = yourvalue
By default, the passwordless integration will enforce the use of the āPlatform authenticator (2fa)ā factor. To override this default, the property idp.duo.oidc.passwordless.allowedFactors can be set to a comma-delimited list to accept. These values are based on what Duo returns in the āfactorā claim in its responses and their consistency/stability is at present unclear, and may depend on the exact Duo service version/level one is using as well as when and how a particular credential was enrolled. Over time, Duo adds support for new technologies in ways that alters how newly enrolled credentials may appear but will not āretroactivelyā update older ones.
Multiple integrations are supported (in the manner described in the main plugin documentation) by means of a dedicated bean named shibboleth.authn.DuoOIDC.Passwordless.DuoIntegrationStrategy, though we anticipate this being less of a need for this use case.
In the event of a requirement to directly define new integrations, the parent bean shibboleth.authn.DuoOIDC.Passwordless.DuoIntegration can be used, and it automatically configures the integration to be recognized by this feature as intended for this use case. A standard integration defined using the original parent bean will not be accepted for this purpose, to limit accidents.
Eligibility / Opt-In Control
The creation of the (normally required) cookie to allow use of this feature is managed through an opt-in mechanism that runs after ātraditionalā use of the plugin as a second factor solution. Based on various deductions, the code will clear an existing cookie if warranted and/or evaluate a defined condition to determine whether to offer the subject the choice to opt-in to create a new cookie to allow the feature, essentially an agreement to see the passwordless option presented in the future so that it isnāt a surprise.
A bean of type Predicate<ProfileRequestContext> is invoked after a ātraditionalā Duo login completes. If the condition evaluates to false, than any existing opt-in cookie is cleared, and the flow finishes in the ordinary way. If true, then the subject is āeligibleā to opt-in for passwordless in the future.
The default implementation of this condition is primitive and merely checks whether the factor used during the request is one of the āallowedā factors governed by the idp.duo.oidc.passwordless.allowedFactors property mentioned above. This acts as a signal that the subject āshouldā be able to successfully perform passwordless authentication in the future, but that decision is left to an opt-in form that obviously can be tailored to local needs with documentation, etc. Notably, it would be possible to add a meta-refresh to that page to automate the acceptance process should that be desired, but it was felt that simply āswitchingā the login experience on people without a choice to do so would be suboptimal.
The most obvious use of this extension point is to introduce identity-based considerations into the decision, such as an attribute-driven check for membership in a pilot group to allow gradual/limited adoption of the feature.
This logic can be replaced entirely by setting the property idp.duo.oidc.passwordless.guardCondition to the name of the bean to use; you may also leverage the bean named shibboleth.authn.DuoOIDC.Passwordless.DefaultCondition to compose your own logic with the default implementation.
If the subject elects not to opt-in, this decision is also recorded in the cookie to avoid asking over and over.
Event Handling
This flow uses more event signals than is common in the IdP because it deals with a number of different scenarios triggered by the subjectās input and is designed to allow for a significant amount of control by the MFA flow to respond to those signals.
The following events may be signalled by the flow in various cases to distinguish the outcome:
RequestUnsupported
This is a general event that indicates the flow could not attempt passwordless authentication for technical or data-driven reasons, generally because either:
The flow could not determine the username to present in the view, typically because the guard cookie did not exist or couldnāt be read and decrypted. This is the typical ānot primedā case if the flow is run ahead of the subject opting into the capability.
Or because of an atempt to perform passwordless authentication for a request using a non-browser profile or with the āpassiveā option applied, which is not possible. This allows the MFA logic to avoid extra checking for these conditions and allow the flow to detect them if desired.
ReselectFlow
Indicates the subject pressed the āPassword Loginā button (or whatever you may have customized it to display). This is the signal for āI donāt want to do Passwordless even if you think I canā.
IdentitySwitch
Indicates the subject clicked the āNot You?ā link (or whatever you may have customized it to display). This is the signal for āYou thought I was Alice, but Iām Bob, so I canāt do this.ā
InvalidCredentials
Usually indicates that the subject completed the Duo challenge with a factor that is disallowed, such as a bypass code.
Bypassing the Initial View
If desired, it is possible to leverage the MFA logic used to āprepareā the request ahead of invoking the plugin flow to inject the username to use instead of relying on the cookie and to bypass the normal view presented if desired. The DuoPasswordlessContext (primary usage of which is described below in the examples) can be pre-populated with a username (bypassing reading that from a cookie) and the bypassView flag can be set to true to skip display of the view and directly pass into the Duo service using the pre-set username.
You may set the username and keep the view, but if you bypass the view, you MUST set the username as well or the flow will fail.
Opt-in Cookie Management
The API bean shibboleth.authn.DuoOIDC.Passwordless.CookieManager of type PasswordlessCookieManager is available to support deployer manipulation of the opt-in cookie, including creating it if desired via a different mechanism. Note that the opt-out value is handled in a special way and you will need to test that method explicitly to distinguish between the cookie being absent and the opt-out being present.
Administrative Management Flow
In addition to providing the API bean, a new administrative flow is also provided to allow for direct user or administrator control over the state of the opt-in/out cookie on a device. This flow lives at /idp/profile/admin/DuoOIDC
and defaults to requiring user authentication to access it (most previous admin flows tend to be system-related and are accessed via command line tools via address-based access control). There are two different properties defined that control access to this flow:
idp.duo.oidc.admin.accessPolicy
This controls overall user access and defaults to a policy that doesnāt actually exist in the IdPās access control configuration at present, so it has to be defined to allow access (or changed to a policy that does exist). More on this below.
idp.duo.oidc.admin.adminAccessPolicy
This controls an additional feature of the flow that allow an administrator to not only control the cookie, but actually allows control over the username that appears inside the cookie (normal user access only allows a cookie to be created with the authenticated principal name in it, i.e. the user themself).
The defauly policy for user access is named āAccessByAnyoneā and can be added to the map in conf/access-control.xml like so:
...
<entry key="AccessByAnyone">
<bean parent="shibboleth.PredicateAccessControl" c:_0-ref="shibboleth.Conditions.TRUE" />
</entry>
...
The flow itself relies on a view template in views/admin/passwordless-admin.vm. The view provides a form that includes some simple logic at the top allowing it to determine the current state of the cookie and whether the user was granted admin access or merely control over oneās own cookie, and then provides the buttons to set an opt-in or opt-out cookie or clear it entirely. Admin access allows the username to be set explicitly rather than derived.
In the event a user actually clicks the āFinishā button (which frees resources devoted to the flow conversation), it will redirect them to a location set by the property idp.duo.oidc.admin.landingPage
This is to some degree an example/demo of how one might provide this sort of functionality but because the cookie is generally scoped to the IdP, it is more or less assumed that a mechanism living within the IdP or at least running on the same server would need to be used to do so.
Note that allowing access to this flow would by default allow anybody to opt-in to the feature without passing the usual eligibility check that applies to the mainline behavior, but one could of course include checks for particular attributes (e.g., a group membership) in an access policy if desired.
Examples
MFA Flow Configuration Example
The use of this feature is controlled by means of an external signal, the creation of a new context object of the type net.shibboleth.idp.authn.plugin.duo.context.DuoPasswordlessContext. The contents donāt necessarily matter but the existence of the object in the tree acts as a signal that the passwordless āmodeā of the plugin could or should be engaged when possible. Absent the attached context object, everything runs as usual, though the plugin will perform the opt-in check and presentation in the event that the fundamental property to enable the passwordless feature is set, which is likely not desirable.
In a typical scenario, we assume the use of passwordless authentication to the extent possible, and fall back to a traditional login process (in this example thatās just Password followed by Duo conditionally based on whether the application requires it). That is, while not every application may actually require stronger authentication, we assume that the whole idea is that using this approach should be ābetterā for the user than using a password anyway, so thereās no need to āpreventā its use simply because itās not required. This differs from the traditional use of Duo, which is viewed as an āextraā step and a source of user annoyance.
A complete example follows. It is largely Spring boilerplate around the scripts and rules used to drive the behavior. The initial step is to run a script bean (tryPasswordless), and detect the ānon-successfulā expected results.
In a successful case, nothing else is done, the flow simply completes using the result produced. In one of the two other cases, the flow instead does what it normally tends to do (run the Password flow followed by conditionally running the Duo flow), but with a pre-step script (āfirstFactorā) that clears the special context object from the tree to revert the behavior of the Duo flow if it in fact is run again.
As a general matter, most use cases will likely involve those basic steps and scripts, with specific logic extending the behavior probably added to them in special cases, but the fundamental steps are probably the right ones for most sites.
Optimizing the Initial Decision
A optional approach that isnāt much more complex allows the initial MFA rule to check for the opt-in cookie and pre-populate it into the context before running the Duo flow, or if itās absent just run the Password flow instead. This skips the extra overhead of starting up a subflow that will immediately terminate itself after doing a bit of work. The only difference is a change to the tryPasswordless
script bean and injecting a special cookie manager object into the script:
Group-Based Eligibility
A common use case is to manage opt-in testing/piloting of this feature by including a user-based check into the eligibility condition that runs to determine whether the opt-in form is displayed. A group management system like Grouper could be used for example to manage the permissions for this.
Note that this does not inherently prevent somebody from using the administrative flow feature to create the opt-in cookie directly. If this is a concern, you would want to apply this group check to the general access control rule for end-user access to that feature and toggle the property on to resolve attributes after logging in to access that feature.
An example of performing an attribute lookup for a group is demonstrated below. Note that it also shows how to combine a more āexpensiveā check with the built-in condition based on Duo factor in an AND condition so that if the user doesnāt demonstrate the ability to use a passwordless factor first, the group check is skipped.
User Verifying Roaming Authenticators
The default PasswordlessCondition works very well with authenticator types that always include user verification, as is the case with Platform Authenticators. Many Security Key devices support both simple presence verification in addition to user verification. Duoās current implementation allows only requiring or discouraging user verification ā if it asks the client for it, it must be provided ā there is no option to allow (fall through to) presence verification if the user verification process can not be completed. This means you would not want to add 'Roaming authenticator (2fa)' to the list of allowedFactors, since the ātraditionalā second factor Duo process will typically not be configured to require user verification and could allow the single-factor security key through or bring the user to a an MFA test that can not be passed, depending on the how much the configuration of the Duo Application profile used during Passwordless authentication differs from the profile used during the traditional second factor authentication.
An example that uses the AdminAPI access feature of the plug-in to determine if the roaming authenticator used by the accountholder is known to be capable of user verification is below. As written, this example would replace the built-in check, but could easily be combined with that check via the method shown in the example above.
Note: Duo determines the capabilities of the authentication device during enrollment, and does not appear to update them during the authentication process. As noted above, the information used during this check may be inaccurate for that and other reasons. At a minimum, some authentication devices may need to be removed and re-enrolled to return expected results.