WebAuthnRegistration

Overview

The plugin comes with an administration flow for registering and managing FIDO2 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 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.

Configuration Steps

Other than following step 1, you do not need to change any of the default registration options to register and use a FIDO2 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

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:

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

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

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

  2. ensure the attribute resolver is enabled after authentication, idp.authn.webauthn.admin.registration.resolveAttributes=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 bean of type Function<ProfileRequestContext, byte[]>.

User Name Population

The WebAuthn name (user.name) is a human-palatable identifier for a user’s account a credential is associated with. This is separate from the internal username the IdP uses to store the credential against and is only used by the authenticator to help the user select the correct credential to authenticate with. 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 in conf/authn/webauthn-config.xml referenced by the idp.authn.webauthn.registration.name.strategy in conf/authn/webauthn.properties.

You may decide to pull this value from the attribute resolver. This is supported out of the box by utilizing a built-in strategy, which can be activated by modifying the following properties in conf/authn/webauthn.properties:

  1. change the property idp.authn.webauthn.registration.name.strategy to reference the bean shibboleth.authn.webauthn.registration.AttributeContextWebAuthnNameLookupStrategy.

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

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

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

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

  2. ensure the attribute resolver is enabled after authentication, idp.authn.webauthn.admin.registration.resolveAttributes=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 bean of type Function<ProfileRequestContext, String>.

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

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

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 WebAuthn RP) to verify the provenance of the authenticator used when registering a FIDO2 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 FIDO2 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

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

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

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.

Credential Registration Policies

The registration flow comes with a basic, extendable, policy engine for accepting and rejecting credentials based on the authenticator that created them. To enable policy checks, set the property idp.authn.webauthn.registration.authenticator.policy.enabled to true in conf/authn/webauthn-registration.properties.

Policies typically work off an Authenticator’s Attestation GUID (AAGUID) and require attestation statements from the authenticator. See the attestation conveyance section on how to configure this.

The default policy is defined by a list shibboleth.authn.WebAuthn.registration.ChainedRegistrationPolicyList of policies configured in conf/authn/webauthn-registration-config.xml. Out of the box, the following policies are included:

Policy Name

Description

Value

Policy Name

Description

Value

AllowlistAuthenticatorPolicy

An allowed list of authenticators based on their Authenticator Attestation GUID (AAGUID)

The comma-separated list of authenticators can be directly specified in the XML configuration or, for convenience, set by the idp.authn.webauthn.registration.authenticator.policy.allowedAuthenticators property.

 

 

Reference

 

Â