Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Note

Alpha software: this plugin is only a prototype at the moment and should not be used in production!

Info

Status: alpha version Release Candidate version 0.9.0 released. Requires IdP v5 and onward.

...

The authn/WebAuthn login flow supports the Web Authentication API (WebAuthn) as part of the FIDO2 standard—enabling FIDO2 authentication. This allows public-key-based strong authentication of users. The plugin can operate as either a single-factor within a wider multi-factor authentication, as a first-factor where the username is supplied by the user (passwordless flow), or as a first-factor where the username is not supplied by the user (usernameless flows using passkeys).

...

Plugin

Plugin ID

Module(s)

Depends On

Authentication Flow ID

Latest Version

Bug Reporting

WebAuthn Authentication Plugin

net.shibboleth.idp.plugin.authn.webauthn

idp.authn.WebAuthn

authn/WebAuthn

0.9.0.3-alphaRC

JWEBAUTHN

Installation of Pre-release Plugin

...

The WebAuthn flow is capable of running as either a second , single-factor of authentication (similar to U2F, but using the WebAuthn APIs) or as a first and only factor of authentication in which can still satisfy multi-factor mode requirements (passkeys for example incorporate incorporating two factors; something the user has and something the user is). When acting in a first-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.

...

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 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.

...

Anchor
2fa-authn
2fa-authn
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 factor of authentication must have produced an Authentication Result from the MFA context. Finally, a principal name must be found through a lookup strategy, which is done 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.

...

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

...

Before you start, make sure to enable the MFA login module and configure the flow in conf/authn/authn.properties. The WebAuthn plugin operates within an MFA flow, even 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 first-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

...

Note

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.

WebAuthn as the sole factor but with two-factor authentication fallback for registration

TODO: Use a different 2fa for registration when WebAuthn credentials are not registered

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 WebAuthn credentials. This requires the registration step to provide its own username collection page so a decision can be made about which flow to follow in the event the user has not registered any credentials. This only affects registration, if a user just performs authentication with no WebAuthn credentials 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.

...

Anchor
CustomEventsOnNoCreds
CustomEventsOnNoCreds
Signalling custom events when the user has no registered credentials

In case the user has not registered any WebAuthn 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.registrationpasswordless.collectUsername propertyto 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.

...

In case the user has not registered any WebAuthn 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.

...

titleCustom 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

...

languagexml

...

signalEventOnNoCredentialsto 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 WebAuthn 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.signalEventOnNoCredentialsRegisteredForUserHandleto 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 WebAuthn credential. The event is signalled just beforethe 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).

Expand
titleCustom 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

Code Block
languagexml
<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" />
  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.
    --><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

Code Block
languagexml
<util:map id="shibboleth.authn.MFA.TransitionMap">
    
        <entry key="">
          <!-- Custom error events to reflect back from user-supplied login subflows. --><bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/WebAuthn" />            <end-state
id="NoRegisteredWebAuthnCredentials" />     <end-state id="NoCredentialsRegisteredForUserHandle" /></entry> 
        
<global-transitions>         <transition<entry onkey="NoCredentialsRegisteredForUserHandleauthn/WebAuthn">
to="NoCredentialsRegisteredForUserHandle" />         <transition on="NoRegisteredWebAuthnCredentials" to="NoRegisteredWebAuthnCredentials" />  <bean parent="shibboleth.authn.MFA.Transition">
                <transition<property onname="#{!'proceed'.equals(currentEvent.id)}" to="InvalidEvent" />nextFlowStrategyMap">
       </global-transitions>  </flow> 

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

conf/authn/mfa-authn-config.xml

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

                       <entry key="NoRegisteredWebAuthnCredentials" value="SomeOtherFlow" />
                        <entry key="NoCredentialsRegisteredForUserHandle" value="SomeOtherFlow" />
                    </map>
                </property>
            </bean>
        </entry> 

    </util:map></util:map>
Note

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).

...

The default credential repository uses the Shibboleth InMemory Storage Service. Any Storage Service should be compatible with the plugin.

TODO. More on the credential repository

...

The plugin comes with an administration flow for registering and managing WebAuthn credentials. The inbuilt flow represents the minimum viable product for implementing such a feature. In the future other plugins may provide this functionality.

The registration flow can be accessed by navigating to:

http[s]://hostname/idp/profile/admin/webauthn-registration

The registration flow collects the username as a first step so it can look up any registered credentials before passing control to the authentication system. This information can be accessed in, say, the MFA configuration using the isWebAuthnAvailable() method on the WebAuthnRegistrationContext, to decide which flow to use. The example MFA Flow With Password Bypass is a suitable initial setup that allows users to register their first credentials upon inputting their username and password; other combinations are possible.

Overview of Configuration Steps

Other than following step 1, you do not need to change any of the default registration options to successfully register and use a WebAuthn credential with the IdP. If you do want more control over the process, there are a few options available:

  1. (Required) Configure a suitable access control policy.

  2. (Optional) Configure how user account details are passed to the WebAuthn API and authenticator.

  3. (Optional) Decide what type of authenticator you want to support.

  4. (Optional) Decide if you want to require only ‘trusted’ (from FIDO Alliance Metadata) authenticators to be registered

...

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 WebAuthn credentials. This requires the registration step to provide its own username collection page so a decision can be made about which flow to follow in the event the user has not registered any credentials. This only affects registration, if a user just performs authentication with no WebAuthn credentials 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 propertyto 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.accessPolicy. The default policy, AccessByCurrentUser, is not defined in the IdP's access control configuration and needs to be added for the flow to load: add it to the map in conf/access-control.xml like so:

...

languagexml

...

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 two-factor fallback for authentication and registration

Enabling event signalling when a user does not yet have a registered WebAuthn 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.

Expand
titleWebAuthn MFA Flow With Password and Duo fallback
Expand
titleExample AccessByCurrentUser Policy Using A Custom Comparison Predicate

conf/authn/mfa-authn-config.xml

Code Block
<util:map id="shibboleth.authn.MFA.TransitionMap">
    
     
<bean parent
   <entry key="
shibboleth.PredicateAccessControl
">
            
<constructor-arg>
<bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkPasswordOrWebAuthnForRegistration" />            
     
<bean
 
id="AccessByCurrentUserPredicate"
  </entry>       
        
   
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AllowCurrentUserAccessPredicate">
      <entry key="authn/WebAuthn">
            <bean parent="shibboleth.authn.MFA.Transition">
                <property name="nextFlowStrategyMap">
               
</bean>
     <map>        
</constructor-arg>
  
      
</bean>
     
</entry> ...

This policy is essential if you plan on creating an MFA flow (such as MFA Flow With Password Bypass) which provides another authentication method if the user has no registered credentials: either using isWebAuthnAvailable() or when signalling a custom event.

The AccessByCurrentUser policy checks the username found in the registration context—collected by the username view when the user first enters the registration flow—is the same as the principal name (identity) of the user that authenticated. This is important, if we imagine a scenario where a nefarious user wanted to ‘change' or possibly 'downgrade’ a user’s authenticate method from WebAuthn to, say, username and password (or whatever else was configured as a ‘backup’), all they would need to do is enter a non-existent username into the username collection step of the registration page and then have the MFA flow direct them to a different flow (because they do not have any registered credentials) where, for example, they can proceed to try different usernames and passwords for authentication. Importantly, the registration flow uses the principal name of the authenticated user to register credentials against.

The AccessByCurrentUser policy serves as a safeguard by ensuring that the username initially entered into the registration page—for which the credentials are retrieved, and any flow decision is based—matches the authenticating user's principal name. If they do not match, access to the registration page is denied. This measure prevents a user from switching usernames between the registration and authentication flows.

However, there are some caveats to consider. The principal name that results from authentication is determined by the Authentication flow, the SubjectCanonicalization flow, and possibly any transformations that have been applied to the username. It is, therefore, entirely possible (although probably not common) that the user who entered the registration flow is the same as the user who authenticated, but their username and principal name do not match. Generally, if this is the case, a different comparison predicate would need to be created and plugged into the AccessByCurrentUser policy: see the example below.

Code Block
languagexml
<entry key="AccessByCurrentUser">
    <bean parent="shibboleth.PredicateAccessControl">             <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");
              <constructor-arg>  mfaCtx = authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext");
        <bean id="AccessByCurrentUserPredicate"       if (mfaCtx.isAcceptable()) {
        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AllowCurrentUserAccessPredicate">                 <property name="comparisonPredicate">
nextFlow = null;
                }
  <bean id="StringMatchComparisonPredicate"             nextFlow;   // pass control to second factor or end  class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AllowCurrentUserAccessPredicate$DefaultCurrentUserComparisonPredicate">
with the first
                         <property name="comparisonPredicate"]]>
            </value>
        </constructor-arg>
        <bean ...
                              </bean>
                          </property>
                      </bean>                        
                </property>
            </bean>
        </constructor-arg>
    </bean>
</entry>

If the applied transformations are simple enough and the simple-subject-c14n (or similar) flow is used to expose the UsernamePrincipal as the principal name, it may be feasible to match the username entered in the registration process by adjusting the following properties.

  1. idp.authn.webauthn.registration.username.uppercase : upper case the username?

  2. idp.authn.webauthn.registration.username.lowercase : lowercase the username?

  3. idp.authn.webauthn.registration.username.trim : trim the username?

And supplying any transformations by defining the bean, shibboleth.authn.webauthn.registration.UsernameTransformations.

Note, if you do this and you are using the passwordless authentication mode, you will need to supply the same transformation settings to the authentication flow, otherwise, it will not match with the username used to register the credential.

  1. idp.authn.webauthn.passwordless.username.uppercase : upper case the username?

  2. idp.authn.webauthn.passwordless.username.lowercase : lower case the username?

  3. idp.authn.webauthn.passwordless.username.trim : trim the username?

And supplying any transformations by defining the bean, shibboleth.authn.webauthn.passwordless.UsernameTransformations.

...

During registration, the IdP will pass user account details to the WebAuthn API. Some of this information is used to improve the user experience whilst creating credentials, and some are used by the authenticator to bind credentials to user accounts at the IdP.

The generation of this information is described in the following sections.

User ID (UserHandle) Population

The user ID (user.id) should be the primary key of the user account within the IdP, it must not exceed 64 bytes in length, and It should not include personally identifiable information. The ID should not change and is used by the authenticator to bind a credential to a user during registration with the IdP. In an authentication response, it is returned as the user handle.

In this context, the credential is registered against the IdP and so the user ID of the credential will be the same no matter who the requesting, upstream, service provider is—an authenticator will only store one credential for the IdP per user ID.

By default, the user ID is generated at runtime using a random byte sequence of 64 bytes. However, you may prefer to pull this value from the attribute resolver. This is supported by changing the following properties in conf/authn/webauthn.properties:

  1. change the property idp.authn.webauthn.registration.userid.strategy to reference the bean shibboleth.authn.webauthn.AttributeContextUserIdLookupStrategy.

  2. ensure the attribute resolver is enabled after authentication, idp.authn.webauthn.admin.registration.resolveIdentityAttributes=true.

  3. decide which attribute from the resolver context to use using idp.authn.webauthn.registration.userid.attributeId.

    1. Note, the AttributeContextUserIdLookupStrategy requires the attribute to be a single StringAttributeValue converted to a byte array assuming a UTF-8 character set.

Complete control over the strategy is possible by defining your own strategy of type Function<ProfileRequestContext, byte[]>.

...

The user name (user.name) is a human-palatable identifier for a user’s account a credential is associated with. It is only intended for display purposes. It may be truncated by the authenticator to 64 bytes.

By default, this is taken from the principal name of the user who authenticated (contained in the SubjectContext). This can be changed by creating a bean named idp.authn.webauthn.registration.usernameLookupStrategy in conf/authn/webauthn-config.xml.

TODO: you could use the same strategy as user display name population if you want to take it from the attribute context

User Display Name Population

The user display name (user.displayName) is a human-palatable name for the user’s account. It is only intended for display to the user during registration. It is not used during authentication.

By default, the user display name is taken from the principal name in the SubjectContext. That is, the canonical principal name of the subject that authenticated to the registration endpoint. However, you may prefer to pull this value from the attribute resolver. This is supported by changing the following properties in conf/authn/webauthn.properties:

  1. change the property idp.authn.webauthn.registration.displayname.strategy to reference the bean shibboleth.authn.webauthn.AttributeContextDisplayNameLookupStrategy.

  2. ensure the attribute resolver is enabled after authentication, idp.authn.webauthn.admin.registration.resolveIdentityAttributes=true.

  3. decide which attribute from the resolver context to use using idp.authn.webauthn.registration.displayname.attributeId.

Complete control over the strategy is possible by defining your own strategy of type Function<ProfileRequestContext, String>.

...

Discoverable Credentials (Passkeys)

TODO

Authenticator Attachment Options

TODO

Attestation Conveyance

TODO

User Verification

TODO

</bean>

Anchor
CredentialRepository
CredentialRepository
Credential Repository

The plugin's critical component is the credential repository which stores and loads credential registrations. The default credential repository utilizes the Shibboleth Storage Service, but it's also possible to utilize other repository implementations by extending the WebAuthnCredentialRepository interface. The default repository uses the configured shibboleth.StorageService, although it is possible to override this by specifying a different bean in the idp.authn.webauthn.StorageService property.

In theory, any implementation of a storage service should be compatible, but it's important to consider its capabilities before using it. For example, for testing, you can use client-storage by referencing (in that property) the bean shibboleth.ClientSessionStorageService. But that will store your credential registrations in the browser and is not portable across browsers—although the credentials will survive an IdP restart so it might be useful during initial testing.

In production, you might want to use a JDBC storage option. The following example shows how this might work.

Expand
titleMariaDB Credential Repository Example

Assuming you do not already have a database suitable for use with the Shibboleth Storage Service (if you do, you can skip to step 3):

1. install the JDBC storage plugin and create a new schema/database (e.g. webauthn) and a new table (e.g. webauthn.StorageRecords):

Code Block
CREATE SCHEMA IF NOT EXISTS `webauthn`;

CREATE TABLE webauthn.StorageRecords (
  context varchar(255) NOT NULL,
  id varchar(255) NOT NULL,
  expires bigint DEFAULT NULL,
  value text NOT NULL,
  version bigint NOT NULL,
  PRIMARY KEY (context, id)
);
  1. Add the following beans to conf/global.xml:

Code Block
    <bean id="shibboleth.JDBCDataSource" class="org.mariadb.jdbc.MariaDbDataSource">
        <property name="url" value="jdbc:mariadb://localhost:3306/webauthn" />
        <property name="user" value="<user>" />
        <property name="password" value="<password>" />
    </bean>
    
    <bean id="shibboleth.WebAuthnStorageService" parent="shibboleth.JDBCStorageService"
        p:cleanupInterval="%{idp.storage.cleanupInterval:PT10M}" 
        p:dataSource-ref="shibboleth.JDBCDataSource"/>
  1. Finally, set the storage service bean you want to use for WebAuthn (shibboleth.WebAuthnStorageService in this example) using the property idp.authn.webauthn.StorageService in conf/authn/webauthn.properties.

Anchor
WebAuthnRegistrationFlow
WebAuthnRegistrationFlow
Credential Registration Flow

The plugin comes with an administration flow for registering and managing WebAuthn credentials. The inbuilt flow represents the minimum viable product for implementing such a feature. In the future other plugins may provide this functionality.

The registration flow can be accessed by navigating to:

http[s]://hostname/idp/profile/admin/webauthn-registration

The registration flow collects the username as a first step so it can look up any registered credentials before passing control to the authentication system. This information can be accessed in, say, the MFA configuration using the isWebAuthnAvailable() method on the WebAuthnRegistrationContext, to decide which flow to use. The example MFA Flow With Password Fallback is a suitable initial setup that allows users to register their first credentials upon inputting their username and password; other combinations are possible.

Overview of Configuration Steps

Other than following step 1, you do not need to change any of the default registration options to register and use a WebAuthn credential with the IdP successfully. If you do want more control over the process, there are a few options available:

  1. (Required) Configure a suitable access control policy.

  2. (Optional) Configure how user account details are passed to the WebAuthn API and authenticator.

  3. (Optional) Decide what type of authenticator you want to support.

  4. (Optional) Decide if you want to require only ‘trusted’ (from FIDO Alliance Metadata) authenticators to be registered

Anchor
ACPolicy
ACPolicy
AccessPolicy Configuration

The user and client accessing the registration flow are subject to an AccessControlConfiguration set by the property idp.authn.webauthn.admin.registration.accessPolicy. The default policy, AccessByCurrentUser, is not defined in the IdP's access control configuration and needs to be added for the flow to load: add it to the map in conf/access-control.xml like so:

Code Block
languagexml
...
    <entry key="AccessByCurrentUser">
        <bean parent="shibboleth.PredicateAccessControl">
            <constructor-arg>
                <bean id="AccessByCurrentUserPredicate" 
                    class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AllowCurrentUserAccessPredicate">
                </bean>
            </constructor-arg>
        </bean>
    </entry>
...

This policy is essential if you plan on creating an MFA flow (such as MFA Flow With Password Bypass) which provides another authentication method if the user has no registered credentials: either using isWebAuthnAvailable() or when signalling a custom event.

The AccessByCurrentUser policy checks the username found in the registration context—collected by the username view when the user first enters the registration flow—is the same as the principal name (identity) of the user that authenticated. This is important, if we imagine a scenario where a nefarious user wanted to ‘change' or possibly 'downgrade’ a user’s authenticate method from WebAuthn to, say, username and password (or whatever else was configured as a ‘backup’), all they would need to do is enter a non-existent username into the username collection step of the registration page and then have the MFA flow direct them to a different flow (because they do not have any registered credentials) where, for example, they can proceed to try different usernames and passwords for authentication. Importantly, the registration flow uses the principal name of the authenticated user to register credentials against.

The AccessByCurrentUser policy safeguards by ensuring that the username initially entered into the registration page—for which the credentials are retrieved, and any flow decision is based—matches the authenticating user's principal name. If they do not match, access to the registration page is denied. This measure prevents a user from switching usernames between the registration and authentication flows.

However, there are some caveats to consider. The principal name that results from authentication is determined by the Authentication flow, the SubjectCanonicalization flow, and possibly any transformations that have been applied to the username. It is, therefore, entirely possible (although probably not common) that the user who entered the registration flow is the same as the user who authenticated, but their username and principal name do not match. Generally, if this is the case, a different comparison predicate would need to be created and plugged into the AccessByCurrentUser policy: see the example below.

Expand
titleExample AccessByCurrentUser Policy Using A Custom Comparison Predicate
Code Block
languagexml
<entry key="AccessByCurrentUser">
    <bean parent="shibboleth.PredicateAccessControl">
        <constructor-arg>
            <bean id="AccessByCurrentUserPredicate" 
                class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AllowCurrentUserAccessPredicate">
                <property name="comparisonPredicate">
                    <bean id="StringMatchComparisonPredicate"
                        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AllowCurrentUserAccessPredicate$DefaultCurrentUserComparisonPredicate">
                          <property name="comparisonPredicate">
                              <bean ...
                              </bean>
                          </property>
                      </bean>                        
                </property>
            </bean>
        </constructor-arg>
    </bean>
</entry>

If the applied transformations are simple enough and the simple-subject-c14n (or similar) flow is used to expose the UsernamePrincipal as the principal name, it may be feasible to match the username entered in the registration process by adjusting the following properties.

  1. idp.authn.webauthn.registration.username.uppercase : upper case the username?

  2. idp.authn.webauthn.registration.username.lowercase : lowercase the username?

  3. idp.authn.webauthn.registration.username.trim : trim the username?

And supplying any transformations by defining the bean, shibboleth.authn.webauthn.registration.UsernameTransformations.

Note, if you do this and you are using the passwordless authentication mode, you will need to supply the same transformation settings to the authentication flow, otherwise, it will not match with the username used to register the credential.

  1. idp.authn.webauthn.passwordless.username.uppercase : upper case the username?

  2. idp.authn.webauthn.passwordless.username.lowercase : lower case the username?

  3. idp.authn.webauthn.passwordless.username.trim : trim the username?

And supplying any transformations by defining the bean, shibboleth.authn.webauthn.passwordless.UsernameTransformations.

Anchor
UserIdentityAttributes
UserIdentityAttributes
User Identity for Credential Generation

During registration, the IdP will pass user account details to the WebAuthn API. Some of this information is used to improve the user experience whilst creating credentials, and some are used by the authenticator to bind credentials to user accounts at the IdP.

The generation of this information is described in the following sections.

User ID (UserHandle) Population

The user ID (user.id) should be the primary key of the user account within the IdP, it must not exceed 64 bytes in length, and It should not include personally identifiable information. The ID should not change and is used by the authenticator to bind a credential to a user during registration with the IdP. In an authentication response, it is returned as the user handle.

In this context, the credential is registered against the IdP and so the user ID of the credential will be the same no matter who the requesting, upstream, service provider is—an authenticator will only store one credential for the IdP per user ID.

By default, the user ID is generated at runtime using a random byte sequence of 64 bytes. However, you may prefer to pull this value from the attribute resolver. This is supported by changing the following properties in conf/authn/webauthn.properties:

  1. change the property idp.authn.webauthn.registration.userid.strategy to reference the bean shibboleth.authn.webauthn.AttributeContextUserIdLookupStrategy.

  2. ensure the attribute resolver is enabled after authentication, idp.authn.webauthn.admin.registration.resolveIdentityAttributes=true.

  3. decide which attribute from the resolver context to use using idp.authn.webauthn.registration.userid.attributeId.

    1. Note, the AttributeContextUserIdLookupStrategy requires the attribute to be a single StringAttributeValue converted to a byte array assuming a UTF-8 character set.

Complete control over the strategy is possible by defining your own strategy of type Function<ProfileRequestContext, byte[]>.

Anchor
user.name
user.name
User Name Population

The user name (user.name) is a human-palatable identifier for a user’s account a credential is associated with. During authentication, it will become the UsernamePrincipal of the subject. It may be truncated by the authenticator to 64 bytes.

By default, this is taken from the principal name of the user who authenticated (contained in the SubjectContext). This can be changed by creating a bean named idp.authn.webauthn.registration.UsernameLookupStrategy in conf/authn/webauthn-config.xml.

User Display Name Population

The user display name (user.displayName) is a human-palatable name for the user’s account. It is only intended for display to the user during registration. It is not used during authentication.

By default, the user display name is taken from the principal name in the SubjectContext. That is, the canonical principal name of the subject that authenticated to the registration endpoint. However, you may prefer to pull this value from the attribute resolver. This is supported by changing the following properties in conf/authn/webauthn.properties:

  1. change the property idp.authn.webauthn.registration.displayname.strategy to reference the bean shibboleth.authn.webauthn.AttributeContextDisplayNameLookupStrategy.

  2. ensure the attribute resolver is enabled after authentication, idp.authn.webauthn.admin.registration.resolveIdentityAttributes=true.

  3. decide which attribute from the resolver context to use using idp.authn.webauthn.registration.displayname.attributeId.

Complete control over the strategy is possible by defining your own strategy of type Function<ProfileRequestContext, String>.

Anchor
CredentialGenerationOptions
CredentialGenerationOptions
Credential Generation Options

Discoverable Credentials (Passkeys)

A Discoverable Credential, formally a Resident Key, stores the private key and associated metadata on the authenticator. This allows usernameless flows and is strictly what is required for a WebAuthn credential to be a ‘passkey’. The alternative allows the private key to be stored, in a protected format, at the relying party (for example, packaged inside the credential ID). Noting that some security keys have limited space for Discoverable Credentials.

Technically, a Discoverable Credential can either be synchronised via some sync fabric (a multi-device or synched passkey e.g, cloud-synched via iCloud Keychain) or bound to the device (single-device or device-bound passkey e.g, a security key). Note that currently there is no way in the plugin to set or detect the different types, the W3C work on this is still a working draft.

During the registration of a WebAuthn credential, you can explicitly configure the ResidentKeyRequirement by setting the idp.authn.webauthn.registration.residentKey property. The possible values, set in conf/authn/webauthn.properties, are listed below:

Option

Default

Description

idp.authn.webauthn.registration.residentKey

preferred

Require a residentKey/DiscoverableCredential (passkey) to be created when registering a credential. One-of 'discouraged', 'preferred', 'required'

If you ‘preferred’ Discoverable Credentials during registration but require them for authentication, you may end up in a situation where the user registers a credential they can not later use.

Authenticator Attachment Options

Authenticator attachment modality specifies the mechanism by which a client can communicate with an authenticator. For example, can the authenticator be removed and ‘roam’ across platforms like a security key, or is the authenticator internal to the device and attached to the given platform like a Trusted Platform Module.

This distinction between roaming and platform attachments can be confusing when a platform authenticator can also be used in a roaming context. The most common example of this is that of a mobile device authenticator: when authenticating on the device itself it acts as a platform authenticator, but when authenticating another client via cross-device authentication (QR code and Bluetooth) it acts as a roaming authenticator.

During the registration of a WebAuthn credential, you can explicitly configure the AuthenticatorAttachment by setting the idp.authn.webauthn.registration.authenticatorAttachment property. The possible values, set in conf/authn/webauthn.properties, are listed below:

Option

Default

Description

idp.authn.webauthn.registration.authenticatorAttachment

any

The authenticator attachment (authenticator type) requirement. One-of 'any', 'cross-platform', or 'platform'

Attestation Conveyance

Attestation allows the IdP (acting as a WebAutun RP) to verify the provenance of the authenticator used when registering a WebAuthn credential. This is provided in the form of an attestation statement. Attestation is optional as it can provide a poor user experience (the user must consent to the release of the attestation statement during registration), has an unclear meaning if the credential is synchronised around multiple devices (what created it might not be the same as what eventually uses it), and is a possible privacy concern (adds another data point for fingerprinting).

During registration of a WebAuthn credential, you can explicitly configure the AttestationConveyencePreference by setting the idp.authn.webauthn.registration.attestationConveyancePreference property. The possible values, set in conf/authn/webauthn.properties, are listed below:

Option

Default

Description

idp.authn.webauthn.registration.attestationConveyancePreference

none

How should the attestation be conveyed during registration? One-of 'none', 'indirect', 'direct', or 'enterprise'.

Indirect allows the client to anonymise the attestation. Enterprise may include uniquely identifying information and might be required to only allow organizationally attested authenticators.

Noting, if you want to use the basic support for only allowing credentials to be registered from ‘trusted’ authenticators that are contained within the FIDO metadata feed, you’d typically need to enable ‘direct’ attestation.

User Verification

User verification requires an authenticator to authorise a user to create or use credentials. Typically this involves some kind of authorization guester such as fingerprint recognition, face recognition, or a pin.

During the registration of a WebAuthn credential, you can explicitly configure the AttestationConveyencePreference by setting the idp.authn.webauthn.registration.userVerification property. The possible values, set in conf/authn/webauthn.properties, are listed below:

Option

Default

Description

idp.authn.webauthn.registration.userVerification

discouraged (so a registered key can be used as a second factor as well as a first factor)

Require User Verification on registration. One-of ‘discouraged’, ‘preferred’, ‘required’.

Note that if user verification is required during registration, you may exclude authenticators that are usable in second-factor scenarios (which don’t require a user-verification capable authenticator).

Other Registration Options

The following additional registration options can be set in conf/authn/webauthn.properties.

Option

Default

Description

idp.authn.webauthn.preferredPublicKeyParams

EdDSA,ES256,ES384,ES512,RS1,RS256,RS384,RS512

The preferred set of COSE signature algorithms which a created credential will use. The sequence is ordered from the most preferred to the least. The client makes a best effort to create the most preferred it can.

Anchor
FIDOMetadata
FIDOMetadata
FIDO Metadata Service Support

...

Globally enabling the feature requires setting theidp.authn.webauthn.metadata.enabled property. Then, you have a choice of either downloading the metadata blob from the FIDO alliance Metadata Service URL (currently https://mds3.fidoalliance.org/), or, downloading the blob yourself, storing it locally, and pointing to the local file. The metadata does not change very often, but the FIDO guidelines suggest obtaining a new copy every month. Currently, this would require a restart of the IdP to pick up the new metadata.

...

  1. Download and manage the CRLs present in the Metadata blob certificate path manually, and point to them using the idp.authn.webauthn.metadata.crlsproperty.

    1. For the current signing certificate, these are at http://crl.globalsign.com/root-r3.crl and http://crl.globalsign.com/gs/gsextendvalsha2g3r3.crl. You’d need to keep these up to date to a) prevent them from expiring, and b) perform meaningful revocation checks.

    Comment out , and point to them using the idp.authn.webauthn.metadata.crlsproperty and enable automated CRL checking from an online distribution point via the system property com.sun.security.enableCRLDP (as described in the CertPathDocs).

    1. This will enable CRL distribution points for the Sun PKIX engine across the IdP. So be careful this is what you want to do before enabling it.

Registration Options

The following registration options can be set in conf/authn/webauthn.properties.

TODO: all these options should have their own sections

...

Option

...

Default

...

Description

  1. .

    1. For the current signing certificate, these are at http://crl.globalsign.com/root-r3.crl and http://crl.globalsign.com/gs/gsextendvalsha2g3r3.crl. You’d need to keep these up to date to a) prevent them from expiring, and b) perform meaningful revocation checks.

  2. Comment out the idp.authn.webauthn.

...

preferred

...

Require a residentKey (passkey) to be created when registering a credential. One-of 'discouraged', 'preferred', 'required'

...

idp.authn.webauthn.preferredPublicKeyParams

...

EdDSA,ES256,ES384,ES512,RS1,RS256,RS384,RS512

...

The preferred set of COSE signature algorithms which a created credential will use. The sequence is ordered from the most preferred to the least. The client makes a best effort to create the most preferred it can.

...

idp.authn.webauthn.registration.authenticatorAttachment

...

any

...

The authenticator attachment (authenticator type) requirement. One-of 'any', 'cross-platform', or 'platform'

...

idp.authn.webauthn.registration.userVerification

...

discouraged (so a registered key can be used as a second factor as well as a first factor)

...

Require User Verification on registration

...

idp.authn.webauthn.registration.attestationConveyancePreference

...

none

...

How should the attestation be conveyed during registration? One-of 'none', 'indirect', 'direct', or 'enterprise'.

...

Info

This is not yet available in the alpha release

In addition to user management of their credentials, there is an admin flow for administrators to manage other users' credentials. Specifically, to search for and remove a user's registered credential from the system.

The management flow can be accessed by navigating to:

http[s]://hostname/idp/profile/admin/webauthn-management

As with the registration flow, the management flow will use whichever authentication method is enabled. Importantly, the client and user accessing the management function are subject to an AccessControlConfiguration set by the property idp.authn.webauthn.admin.management.accessPolicy; by default, this is the AccessByAdminpolicy. Given the purpose of this flow, it is important to ensure a suitably restrictive access policy is set. Furthermore, it is essential to ensure that an appropriate authentication method is executed, even if a fallback has been configured for administrators lacking WebAuthn credentials. This is controlled by the idp.authn.webauthn.admin.management.defaultAuthenticationMethods property, which defaults to saml2/<http://example.org/ac/classes/mfa>. This default setting is intentional, requiring careful consideration of what it should be.

The flow's function is pretty simple, first, you search for a user based on their User Name to list their registered credentials. Then, you can remove one or more of those credentials before finishing the flow.

Debugging Registration and Authentication Requests

...

  1. metadata.crlsproperty and enable automated CRL checking from an online distribution point via the system property com.sun.security.enableCRLDP (as described in the CertPathDocs).

    1. This will enable CRL distribution points for the Sun PKIX engine across the IdP. So be careful this is what you want to do before enabling it.

Anchor
AdminAdminFlow
AdminAdminFlow
Administrative Credential Management Flow

In addition to user management of their credentials, there is an admin flow for administrators to manage other users' credentials. Specifically, to search for and remove a user's registered credential from the system.

The management flow can be accessed by navigating to:

http[s]://hostname/idp/profile/admin/webauthn-management

As with the registration flow, the management flow will use whichever authentication method is enabled. Importantly, the client and user accessing the management function are subject to an AccessControlConfiguration set by the property idp.authn.webauthn.admin.management.accessPolicy; by default, this is the AccessByAdminpolicy. Given the purpose of this flow, it is important to ensure a suitably restrictive access policy is set. Furthermore, it is essential to ensure that an appropriate authentication method is executed, even if a fallback has been configured for administrators lacking WebAuthn credentials. This is controlled by the idp.authn.webauthn.admin.management.defaultAuthenticationMethods property, which defaults to saml2/<http://example.org/ac/classes/mfa>. This default setting is intentional, careful consideration should be given if you change this setting.

The process is straightforward: initially, you search for a user by their User Name to display their registered credentials. Subsequently, you have the option to delete one or more of these credentials before completing the process.

Debugging Registration and Authentication Requests

If you want to easily see and debug both the registration ceremony (PublicKeyCredentialCreationOptions) and authentication ceremony (PublicKeyCredentialRequestOptions), you can set the idp.authn.webauthn.ui.debug property to true in conf/authn/webauthn.properties. The requests should appear on the authentication and registration views in a drop-down box.

Anchor
ExampleVideos
ExampleVideos
Example videos

Video

Description

passwordless--register-authn.mov

Register and use a new WebAuthn credential. Use Password and DuoOIDC MFA to authenticate (for the first time) to the registration page

usernameless--no-auth-register-key-authn.mov

Try usernameless login with no registered credentials (although some exist in the Chrome password manager). Then, register a new credential and use it as a passkey in a usernameless flow.

admin--authn-admin-remove-key-register-new-authn.mov

‘Philsmart’ has a registered credential which can be used to authenticate. The admin ‘pdoe' removes that credential and ‘philsmart’ can no longer authenticate. ‘philsmart’ is forced to register a new credential using Password and DuoOIDC MFA. Once registered ‘philsmart’ can authenticate with their new credential.