Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 23 Next »

Overview

The WebAuthn flow is capable of running as either a second single-factor authentication (similar to U2F, but using the WebAuthn APIs) or as a first and only factor of authentication which could still satisfy multi-factor requirements. For example, device bound passkeys such as those found on a hardware security key, incorporate two factors; something the user has and something the user is. However, multi-device synched passkeys are not, technically, something you have—although they may still provide adequate multi-factor authentication assurances in certain circumstances.

When acting in a single-factor authentication mode, the flow can be configured as either a usernameless (passkey) flow or passwordless flow: this is toggled using the idp.authn.webauthn.usernameless.enabled property.

Authentication Flows

Usernameless (Passkey) flow

A usernameless flow does not require the user to enter their username during authentication. To support a usernameless flow, the authenticator must allow Discoverable Credentials (previously known as a Resident Key and now referred to as a passkey) where the private key and associated metadata is stored on the authenticator (FIDO2 compatible authenticators should work). This is important, as the IdP, without a known username, will not be able to preselect a user and credential to use; this must come instead from the user selecting the correct credential—suitable for the IdPs origin—from the authenticator itself.

During authentication, the authenticator is required to:

  • test the user is present by using some form of authorization gesture (for example, by touching the authenticator or clicking on a key to use), and

  • verify the user’s identity by some form of local authorization (for example, using a pin code or biometric recognition).

Passwordless flow

Collecting the username is the initial step in a passwordless flow, and therefore, it does not require storing credentials on the authenticator. Instead, for example, the credential can be encrypted and stored on the server (possibly in the credential ID sent to the server during registration and returned by the IdP during authentication).

During authentication, the authenticator is required to:

  • test the user is present by using some form of authorization gesture (for example, by touching the authenticator or clicking on a key to use), and

  • verify the user’s identity by some form of local authorization (for example, using a pin code or biometric recognition), and

  • test the credential requested by the IdP and used by the authenticator is allowed (has been registered with the IdP).

Second factor authentication (2FA) flow

The WebAuthn flow will operate in second-factor mode automatically only if three conditions are met. First, the property idp.authn.webauthn.2fa.enabled must be set to true. Second, a previous authentication factor must have produced an Authentication Result from the MFA context. Finally, a principal name must be found through a lookup strategy by default from the C14N context or the Session Context using the CanonicalUsernameLookupStrategy. If these conditions are not met, authentication will revert to either a passwordless or usernameless flow (depending on which is enabled).

The acceptable previous factors can be controlled by listing (comma-separated) authentication flows in the property idp.authn.webauthn.2fa.allowedPreviousFactors. The default is authn/Password.

If enabled, and you want to completely bypass the existing logic such that the WebAuthn flow for 2FA is always used, you can set the property idp.authn.webauthn.2fa.forceSecondFactorFlow to true.

When acting as a 2nd factor of authentication, the username is gathered from the principal name as a result of the first factor by lookup strategy and any credentials registered to that username are retrieved. During authentication, the authenticator is then required to:

  • test the user is present by using some form of authorization gesture (for example, by touching the authenticator or clicking on a key to use), and

  • test the credential used is allowed (has been registered with the IdP).

This mode must be used within an appropriate MFA flow, where authn/WebAuthn is used as the second factor.

Configuration of the MFA flow

Before you start, make sure to enable the MFA login module and configure the flow in conf/authn/authn.properties. It should be possible to run the WebAuthn plugin as a standalone authentication method for the IdP. However, it is designed to work within a multi-factor authentication (MFA) flow. Even when used alone to provide single-factor authentication, this design allows for greater flexibility when configuring authentication routes. For example, if no credentials are available for WebAuthn authentication, it can fall back to using a password.

WebAuthn as the sole factor of authentication

In its simplest form, you can set up the WebAuthn plugin to be the only means of authentication in either usernameless or passwordless modes.

Note, that configuring only WebAuthn authentication will also require a FIDO2 credential to access the registration flow. Hence, unless you have another mechanism of seeding the registration of credentials within the credential repository, the user will not be able to access the registration page: please see the final example for a possible solution to this.

 Sole Factor WebAuthn MFA Config

conf/authn/mfa-authn-config.xml

<util:map id="shibboleth.authn.MFA.TransitionMap">    
        <entry key="">
          <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/WebAuthn" />
      </entry>
  </util:map>  

WebAuthn as a second factor of authentication

As described in the second-factor authentication section, the WebAuthn flow can be used as a second factor of authentication where the user only needs to demonstrate possession of a registered credential (typically via a user gesture such as pressing a physical or virtual button). A simple MFA configuration which runs the Password flow before determining if the WebAuthn flow should run is shown below:

 2nd Factor WebAuthn MFA Config

conf/authn/mfa-authn-config.xml

<util:map id="shibboleth.authn.MFA.TransitionMap">    
        <entry key="">
          <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Password" />
      </entry>
      <entry key="authn/Password">
          <bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkSecondFactor" />
      </entry>
  </util:map>   
    
  <!-- If password flow is not enough, use WebAuthn flow as a 2nd factor. Although that will not work if no credentials -->
  <bean id="checkSecondFactor" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript">
      <constructor-arg>
          <value>
          <![CDATA[
              nextFlow = "authn/WebAuthn";
              // Check if second factor is necessary for request to be satisfied.
              authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext");
              mfaCtx = authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext");
              if (mfaCtx.isAcceptable()) {
                  nextFlow = null;
              }
              nextFlow;   // pass control to second factor or end with the first
          ]]>
          </value>
      </constructor-arg>
  </bean>

WebAuthn as the sole factor but with password fallback for registration

The following MFA configuration establishes the WebAuthn plugin as a sole, single-factor, authentication method. However, it also permits the auth/Password flow to be used if the user is in a WebAuthn Registration flow and has not yet enrolled any FIDO2 credentials. This allows users to register their first FIDO2 credential by authenticating against their username and password (and possibly a different second-factor). After the first credential has been enrolled, there is no fallback option, and subsequent registration attempts will require a FIDO2 credential.

 Sole Factor WebAuthn MFA Config With Password Bypass For Registration

conf/authn/mfa-authn-config.xml

<util:map id="shibboleth.authn.MFA.TransitionMap">    
     <entry key="">
        <bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkPasswordOrWebAuthn" />
    </entry>
</util:map>

<!-- Use the webauthn flow unless a registration context exists and the user does not have any FIDO2 credentials registered,
   then use a password flow.-->
 <bean id="checkPasswordOrWebAuthn" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript">
    <constructor-arg>
        <value>
        <![CDATA[
            nextFlow = "authn/WebAuthn"
            // Check if we can use a WebAuthn flow, or if the user has no credentials available to them use a password flow
            webauthnRegCtx = input.getSubcontext("net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext");
            if (webauthnRegCtx != null){
                if (!webauthnRegCtx.isWebAuthnAvailable()){
                    nextFlow = "authn/Password"
                }
            }       
            nextFlow;   // pass control to WebAuthnFlow
        ]]>
        </value>
    </constructor-arg>
</bean>

If you follow this approach you MUST ensure you use (and think about) a suitable access control policy to prevent users from ‘downgrading’ the authentication mechanism used, see the credential registration section for more information.

Signalling custom events when the user has no registered credentials

In case the user has not registered any FIDO2 credentials, there are two ways to signal a custom event to the IdP. The first signal can be emitted just after collecting the username for the passwordless or 2fa flows. The second signal can be emitted after the authenticator has sent the attestation (authentication) response back to the IdP—this is the only signal you can expect from the usernameless (passkey) flow since there is no username collection step.

For the passwordless and second factor flows, setting idp.authn.webauthn.passwordless.signalEventOnNoCredentials to true will signal a custom event ID (described by the property idp.authn.webauthn.passwordless.noCredentialsEventId) If the user (identified by the username entered in the username input step) has no registered FIDO2 credentials. This will signal a custom event immediately after username collection, ending the flow. To have return controlled to the MFA orchestration layer, the event must be described to the system in the conf/authn/authn-events-flow.xml in the usual way (see the example below).

For all flows, but specifically for a usernameless (passkey) flow, setting idp.authn.webauthn.signalEventOnNoCredentialsRegisteredForUserHandle to true will signal a custom eventID (described by the property idp.authn.webauthn.userHandleNoRegisteredCredentialsEventId) if the user, identified by the userHandle (user.id) in the authenticator's attestation response, does not belong to a known user with at least one registered FIDO2 credential. The event is signalled just before the attestation (authentication) response is validated. Again, the event must be described to the system in the conf/authn/authn-events-flow.xml in the usual way if it is to be used as a transition in the MFA flow (see the example below).

 Custom Event MFA Usage Example

To capture the new event IDs as signals to transition to an end-state of the WebAuthn flow (and not trigger an IdP InvalidEvent):

conf/authn/authn-events-flow.xml

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
      abstract="true">

    <!-- ADVANCED USE ONLY -->
    
    <!--
    You can ignore this file unless you are creating your own custom login subflows that want to
    report custom events in response to unusual error or warning conditions.
    -->

    <!-- Custom error events to reflect back from user-supplied login subflows. -->

    <end-state id="NoRegisteredWebAuthnCredentials" />
    <end-state id="NoCredentialsRegisteredForUserHandle" />

    <global-transitions>
        <transition on="NoCredentialsRegisteredForUserHandle" to="NoCredentialsRegisteredForUserHandle" />
        <transition on="NoRegisteredWebAuthnCredentials" to="NoRegisteredWebAuthnCredentials" />
        <transition on="#{!'proceed'.equals(currentEvent.id)}" to="InvalidEvent" />
    </global-transitions>

</flow>

Example MFA flow configuration that uses the new events after first running the WebAuthn flow:

conf/authn/mfa-authn-config.xml

<util:map id="shibboleth.authn.MFA.TransitionMap">
    
        <entry key="">
            <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/WebAuthn" />            
        </entry> 
        
         <entry key="authn/WebAuthn">
            <bean parent="shibboleth.authn.MFA.Transition">
                <property name="nextFlowStrategyMap">
                    <map>          
                        <entry key="NoRegisteredWebAuthnCredentials" value="SomeOtherFlow" />
                        <entry key="NoCredentialsRegisteredForUserHandle" value="SomeOtherFlow" />
                    </map>
                </property>
            </bean>
        </entry> 

    </util:map>

Using this feature needs careful consideration. You probably do not, for example, want to use signalEventOnNoCredentialsRegisteredForUserHandle as a signal to allow a user to drop back into a username and password flow because anybody could bypass WebAuthn authentication just by using a credential they know does not exist (although the ‘attacker’ would need to have an authenticator with a credential registered for that TLS protected origin, e.g., a previously registered and remove key for a different account).

WebAuthn as the sole factor using custom events to drive a fallback for registration and authentication

Some MFA configurations discussed so far allow a different authentication method to run as a fallback if the user has no registered FIDO2 credentials. This requires the registration step to provide its own username collection page so a decision can be made about which flow to follow when the user has not registered any credentials. This only affects registration, if a user performs authentication without first registering a FIDO2 credential, the authentication flow will ultimately fail. This is probably a good thing, however, for flexibility it is possible to disable username collection on the registration flow and delegate ‘NoCredentials’ signalling to the authentication flow using custom events.

To disable username collection on the registration flow, set the idp.authn.webauthn.registration.collectUsername property to false. Then, configure the MFA flow to trigger a different authentication flow if the user has no credentials—following the examples in the custom events topic. It is worth noting this will affect registration and standard authentication. That is, any user without credentials will end up signalling those custom events, and, if the MFA flow is set up with a fallback flow, they will authenticate using that fallback. For registration you can of course require that the fallback method (all methods really) was of a particular quality by setting the default authentication methods property idp.authn.webauthn.admin.registration.defaultAuthenticationMethods to require MFA (or whatever you want). Similarly, if the Service Provider has requested MFA in their request, the authentication fallback for a standard authentication also needs to support it.

WebAuthn as the sole factor using custom events to drive a different two-factor fallback for authentication and registration

Enabling event signalling when a user does not yet have a registered FIDO2 credential (see the previous section), allows us to configure a different type of two-factor authentication fallback. For example, the following will first run the WebAuthn flow which will fail if the user has no existing registered credentials, it then proceeds to the auth/Password flow and, if required (e.g, requested by the SP), to the authn/DuoOIDC flow. If either registration or admin flows are configured to require MFA (by setting their default authentication methods), they will also fall back to authn/Password and authn/DuoOIDC. Once users have registered their first key, they will proceed only to use the WebAuthn plugin for authentication.

 WebAuthn MFA Flow With Password and Duo fallback

conf/authn/mfa-authn-config.xml

<util:map id="shibboleth.authn.MFA.TransitionMap">
    
        <entry key="">
            <bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkPasswordOrWebAuthnForRegistration" />            
        </entry>       
        
         <entry key="authn/WebAuthn">
            <bean parent="shibboleth.authn.MFA.Transition">
                <property name="nextFlowStrategyMap">
                    <map>          
                        <entry key="NoRegisteredWebAuthnCredentials" value="authn/Password" />
                        <entry key="NoCredentialsRegisteredForUserHandle" value="authn/Password" />
                    </map>
                </property>
            </bean>
        </entry>         
        <entry key="authn/Password">
            <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/WebAuthn" />            
        </entry>
    </util:map>
    
    <!-- If the MFA context is not acceptable from the first factor, run the DuoOIDC flow -->
    <bean id="checkSecondFactor" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript">
        <constructor-arg>
            <value>
            <![CDATA[
                nextFlow = "authn/DuoOIDC";
                // Check if second factor is necessary for request to be satisfied.
                authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext");
                mfaCtx = authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext");
                if (mfaCtx.isAcceptable()) {
                    nextFlow = null;
                }
                nextFlow;   // pass control to second factor or end with the first
            ]]>
            </value>
        </constructor-arg>
    </bean>

Prepopulating the WebAuthn username into the authn/Password flow

When using the passwordless flow with a fallback to authn/Password, you could modify the login.vm view to pre-fill the username input with the username entered into the WebAuthn context. For example, at the top of login.vm:

#set ($webAuthnCtx= $authenticationContext.getSubcontext("net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext"))
#set ($webAuthnUsername = $webAuthnCtx.getUsername())

Which you can then use to automatically fill in the username field:

<input type="text" id="username" name="j_username" #if($webAuthnUsername)readonly#end
                        value="#if($webAuthnUsername)$encoder.encodeForHTML($webAuthnUsername)#end" />

See UserInterface for a detailed look at the IdP’s view templates. 

A malicious party could still alter the username, preventing which would require server-side measures(e.g. a step in the MFA Flow, or a post-login authentication flow). However, there is typically no need to worry about that since the service provider is responsible for determining the strength of the authentication required.

Authentication Context Classes (Supported Principals)

As with all authentication flows, the WebAuthn plugin exposes a collection of supportedPrincipalscompatible with the type of authentication mechanism used. By default, the idp.authn.webauthn.supportedPrincipals property contains placeholder examples for what these could be.

The set you specify for the flow is entirely up to you based on the type of authentication flow you have configured, and what authentication assurances you want. For example, when used as a second-factor in 2FA mode, it seems feasible to signal MFA from the WebAuthn plugin. On the other hand, using the plugin alone, in either passwordless or usernameless modes, may not be enough to signal MFA unless you are certain that the credential is tied to a specific device—which cannot currently be determined from the plugin. Even if the typical passkey process (which requires user verification) combines ‘something you are’ (biometrics) or ‘something you know’ (pin) with ‘something you have’ (an authenticator), if the credential is synchronized (as is common with passkey providers like iCloud Keychain and Google Password Manager), it could be argued that the authenticator does not really qualify as 'something you have'. Either way, it is up to you to decide how best to handle this.

Reference

 Properties

Name

Type

Default

Description

idp.authn.webauthn.usernameless.enabled

Boolean

false

Which type of flow is supported? Usernameless (true) or passwordless (false)

idp.authn.webauthn.2fa.enabled

Boolean

false

Enable this flow to act as second factor authentication

idp.authn.webauthn.2fa.allowedPreviousFactors

String list

authn/Password

Which previous factors are acceptable to allow the WebAuthn flow to act as a second factor of authentication

idp.authn.webauthn.2fa.forceSecondFactorFlow

Boolean

false

Force second factor even if no acceptable previous factors ran

idp.authn.webauthn.2fa.username.strategy

Bean reference

shibboleth.authn.webauthn.CanonicalUsernameLookupStrategy

The bean name of the username lookup strategy. By default, this comes from the principal name established by the first factor.

dp.authn.webauthn.updateSignatureCount

Boolean

true

Should we update an authenticators signature counter inside the credential repository after each successful authentication?

idp.authn.webauthn.passwordless.signalEventOnNoCredentials

Boolean

false

Should an event be built if there are no credentials found for the given user? Only applicable to passwordless authentication.

idp.authn.webauthn.passwordless.noCredentialsEventId

String

NoRegisteredWebAuthnCredentials

If an event is triggered when no credentials are found for the user, what should the event ID be?

idp.authn.webauthn.signalEventOnNoCredentialsRegisteredForUserHandle

Boolean

false

Should a custom event be built if the userHandle supplied by the authenticator during authentication is not related to any registered credentials?

idp.authn.webauthn.userHandleNoRegisteredCredentialsEventId

String

NoCredentialsRegisteredForUserHandle

If an event is triggered when no credentials can be found for the given userHandle, what should the event ID be?

idp.authn.webauthn.passwordless.username.uppercase

Boolean

false

Basic transformations that should be applied to the username that is collected as part of the passwordless flow

idp.authn.webauthn.passwordless.username.lowercase

Boolean

false

Basic transformations that should be applied to the username that is collected as part of the passwordless flow

idp.authn.webauthn.passwordless.username.trim

Boolean

false

Basic transformations that should be applied to the username that is collected as part of the passwordless flow

idp.authn.webauthn.passwordless.c14n.postUsernameFlows

Bean ref

shibboleth.PostLoginSubjectCanonicalizationFlows

The ID of the bean that supplies the c14n flows that are applied to the username entered during the passwordless flow

 Beans

Name

Type

Description

  • No labels