1. Introduction

1.1. Web Authentication

Web Authentication is a new, secure web application authentication specification standardized under W3C. By combining local authentication, public-key authentication, per-origin key management, it provides strong authentication to web sites against authentication process attacks like phishing. Implementation is in progress in major browsers, and the specification offers excellent choices for users who place importance on security and convenience. Initially, the specification was developed as FIDO 2.0 by the FIDO Alliance, but it has been transferred to W3C.

1.2. WebAuthn4J

WebAuthn4J is a portable Java library for WebAuthn and Apple App Attest server side verification.

1.3. WebAuthn4J Spring Security

As a related project of WebAuthn4J, we are developing a wrapper library named WebAuthn4J Spring Security to support WebAuthn with Spring Security. To introduce WebAuthn in an application built with Spring Security, it is better to use WebAuthn4J Spring Security rather than directly using WebAuthn4J.

1.4. Feature

1.4.1. Supported Attestation Statement Formats

All attestation statement formats are supported:

  • Packed attestation

  • FIDO U2F attestation

  • Android Key attestation

  • Android SafetyNet attestation

  • TPM attestation

  • Apple Anonymous attestation

  • Apple App Attest attestation

  • None attestation

1.4.2. Conformance

All mandatory test cases and optional Android Key attestation test cases of FIDO2 Test Tools provided by FIDO Alliance are passed.

Since FIDO2 Test Tools runs the test via the REST API of FIDO2 Transport Binding Profile, it is executed through the REST API implementation provided by WebAuthn4j Spring Security.

1.5. Requirements

1.5.1. Language & Framework

  • Java8 or later

1.5.2. Environment

  • SecureContext (https or the localhost)

1.6. Getting from Maven Central

If you are using Maven, just add the webauthn4j as a dependency:

<properties>
  ...
  <!-- Use the latest version whenever possible. -->
  <webauthn4j.version>0.22.2.RELEASE</webauthn4j.version>
  ...
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>com.webauthn4j</groupId>
    <artifactId>webauthn4j-core</artifactId>
    <version>${webauthn4j.version}</version>
  </dependency>
  ...
</dependencies>

1.7. Source code

Source code for this project is hosted on Github.

git clone git@github.com:webauthn4j/webauthn4j.git

1.8. License

WebAuthn4J is an open source software licensed under Apache 2.0 license.

1.9. Contributing

Interested in helping out with WebAuthn4J? Great! Your participation in the community is much appreciated! Please feel free to open issues and send pull-requests.

2. Quick Start

WebAuthn is an authentication method that a user registers a public key generated by an authenticator to the server in advance and the server verifies the signature generated by the authenticator with the public key at the time of authentication.

This quickstart will focus on how to validate the WebAuthn "attestation" data, which is sent at registration and contains public key and authenticator configuration, and how to validate the WebAuthn "assertion" data, which is sent at authentication and contains signature. Apple App Attest attestation and assertion validation is also explained later.

2.1. WebAuthn verification

2.1.1. WebAuthn Attestation verification

To validate an attestation on authenticator registration, call WebAuthnManager#validate with a RegistrationRequest instance as an argument. If you would like to access the parsed data when an validation error occurred, please use WebAuthnManager#parse to parse the registration request and pass the returned RegistrationData instance to WebAuthnManager#validate method.

The members of RegistrationRequest are the values obtained by the WebAuthn JS API in the front end side. Transmit from the front end side to the server side in some way.

RegistrationParameters is an another argument for WebAuthnManager#parse method, and contains server property and validation conditions.

serverProperty has following members.

  • For origin, please specify Origin of the site that provides WebAuthn authentication. WebAuthn specification tells that the authenticator signs the data including Origin recognized by the browser. WebAuthn4J verifies whether the written Origin (namely, recognized by the browser) matches the specified Origin in serverProperty to prevent phishing attacks, which is a behavior required in WebAuthn specification.

  • For rpId, please set the rpId of the site that provides WebAuthn authentication. rpId is a parameter that specifies the scope of credentials. For more details, please refer to WebAuthn specification rpId definition.

  • For challenge, please specify the Challenge issued on WebAuthn JS API call. challenge is a parameter to prevent replay attacks. By issuing the random byte sequence challenge on server side, signing it with WebAuthn JS API, and verifying the signature on server side, users are protected from the replay attack. It is the application’s responsibility for retaining the issued Challenge.

  • tokenBindingId is a parameter for Token binding. If you do not want to use it please specify null.

If validation fails, an exception inheriting ValidationException is thrown. If validation succeeds, please create an Authenticator instance from the returned value and persist it to the database or something in your application manner. The instance is required at the time of authentication. For more details about Authenticator serialization, please refer Authenticator serialization and deserialization.

// Client properties
byte[] attestationObject = null /* set attestationObject */;
byte[] clientDataJSON = null /* set clientDataJSON */;
String clientExtensionJSON = null;  /* set clientExtensionJSON */
Set<String> transports = null /* set transports */;

// Server properties
Origin origin = null /* set origin */;
String rpId = null /* set rpId */;
Challenge challenge = null /* set challenge */;
byte[] tokenBindingId = null /* set tokenBindingId */;
ServerProperty serverProperty = new ServerProperty(origin, rpId, challenge, tokenBindingId);

// expectations
boolean userVerificationRequired = false;
boolean userPresenceRequired = true;

RegistrationRequest registrationRequest = new RegistrationRequest(attestationObject, clientDataJSON, clientExtensionJSON, transports);
RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, userVerificationRequired, userPresenceRequired);
RegistrationData registrationData;
try {
    registrationData = webAuthnManager.parse(registrationRequest);
} catch (DataConversionException e) {
    // If you would like to handle WebAuthn data structure parse error, please catch DataConversionException
    throw e;
}
try {
    webAuthnManager.validate(registrationData, registrationParameters);
} catch (ValidationException e) {
    // If you would like to handle WebAuthn data validation error, please catch ValidationException
    throw e;
}

// please persist Authenticator object, which will be used in the authentication process.
Authenticator authenticator =
        new AuthenticatorImpl( // You may create your own Authenticator implementation to save friendly authenticator name
                registrationData.getAttestationObject().getAuthenticatorData().getAttestedCredentialData(),
                registrationData.getAttestationObject().getAttestationStatement(),
                registrationData.getAttestationObject().getAuthenticatorData().getSignCount()
        );
save(authenticator); // please persist authenticator in your manner

2.2. WebAuthn Assertion verification

To parse and validate an assertion on authentication, call WebAuthnManager#validate with a AuthenticationRequest instance as an argument. If you would like to access the parsed data when an validation error occurred, please use WebAuthnManager#parse to parse the authentication request and pass the returned AuthenticationData instance to WebAuthnManager#validate method.

The members of AuthenticationRequest are the values obtained by the WebAuthn JS API in the front end side. Transmit from the front end side to the server side in some way.

AuthenticationParameters is an another argument for WebAuthnManager#parse method, and contains server property, persisted authenticator and validation conditions.

// Client properties
byte[] credentialId = null /* set credentialId */;
byte[] userHandle = null /* set userHandle */;
byte[] authenticatorData = null /* set authenticatorData */;
byte[] clientDataJSON = null /* set clientDataJSON */;
String clientExtensionJSON = null /* set clientExtensionJSON */;
byte[] signature = null /* set signature */;

// Server properties
Origin origin = null /* set origin */;
String rpId = null /* set rpId */;
Challenge challenge = null /* set challenge */;
byte[] tokenBindingId = null /* set tokenBindingId */;
ServerProperty serverProperty = new ServerProperty(origin, rpId, challenge, tokenBindingId);

// expectations
List<byte[]> allowCredentials = null;
boolean userVerificationRequired = true;
boolean userPresenceRequired = true;

Authenticator authenticator = load(credentialId); // please load authenticator object persisted in the registration process in your manner

AuthenticationRequest authenticationRequest =
        new AuthenticationRequest(
                credentialId,
                userHandle,
                authenticatorData,
                clientDataJSON,
                clientExtensionJSON,
                signature
        );
AuthenticationParameters authenticationParameters =
        new AuthenticationParameters(
                serverProperty,
                authenticator,
                allowCredentials,
                userVerificationRequired,
                userPresenceRequired
        );

AuthenticationData authenticationData;
try {
    authenticationData = webAuthnManager.parse(authenticationRequest);
} catch (DataConversionException e) {
    // If you would like to handle WebAuthn data structure parse error, please catch DataConversionException
    throw e;
}
try {
    webAuthnManager.validate(authenticationData, authenticationParameters);
} catch (ValidationException e) {
    // If you would like to handle WebAuthn data validation error, please catch ValidationException
    throw e;
}
// please update the counter of the authenticator record
updateCounter(
        authenticationData.getCredentialId(),
        authenticationData.getAuthenticatorData().getSignCount()
);

2.3. Apple App Attest verification

Next, how to verify Apple App Attest is explained. Since Apple App Attest has a data structure similar to WebAuthn, the validator design follows that of WebAuthn. Risk metric evaluation is not supported for now.

2.3.1. Getting from Maven Central

Apple App Attest validators are contained in the dedicated webauthn4j-device-check module. If you are using maven, add the webauthn4j-device-check as a dependency in this way:

<properties>
  ...
  <!-- Use the latest version whenever possible. -->
  <webauthn4j.version>0.13.0.RELEASE</webauthn4j.version>
  ...
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>com.webauthn4j</groupId>
    <artifactId>webauthn4j-device-check</artifactId>
    <version>${webauthn4j.version}</version>
  </dependency>
  ...
</dependencies>

2.3.2. Apple App Attest attestation verification

To validate an attestation on authenticator registration, call DeviceCheckManager#validate with a DCAttestationRequest instance as an argument. If you would like to access the parsed data when an validation error occurred, please use DeviceCheckManager#parse to parse the attestation request and pass the returned DCAttestationData instance to DeviceCheckManager#validate method.

The members of DCAttestationRequest are the values obtained by the Apple App Attest API in the iOS device Transmit from the iOS device to the server side in some way.

DCAttestationParameters is an another argument for DeviceCheckManager#parse method, and contains server property and validation conditions.

DCServerProperty has following members.

  • For teamIdentifier, please set the teamIdentifier used for your iOS App development. For more details, please refer to Validating Apps that connect to your server.

  • For cfBundleIdentifier, please set the cfBundleIdentifier used for your iOS App development. For more details, please refer to Validating Apps that connect to your server.

  • For challenge, please specify the Challenge issued on App Attest API call. challenge is a parameter to prevent replay attacks. By issuing the random byte sequence challenge on server side, signing it with App Attest API, and verifying the signature on server side, users are protected from the replay attack. It is the application’s responsibility for retaining the issued Challenge.

If validation fails, an exception inheriting ValidationException is thrown. If validation succeeds, please create an DCAppleDevice instance from the returned value and persist it to the database or something in your application manner. The instance is required at the time of authentication.

Production environment? Development environment?

Apple App Attest can return a development attestation for development. By default, webAuthn4j-device-check is set to accept a production attestation. If you want to accept a development attestation, you need to DCAttestationDataValidator#setProduction false.

// Client properties
byte[] keyId = null; /* set keyId */
byte[] attestationObject = null; /* set attestationObject */
byte[] challenge = null; /* set challenge */
byte[] clientDataHash = MessageDigestUtil.createSHA256().digest(challenge);

// Server properties
String teamIdentifier = null /* set teamIdentifier */;
String cfBundleIdentifier = null /* set cfBundleIdentifier */;
DCServerProperty dcServerProperty = new DCServerProperty(teamIdentifier, cfBundleIdentifier, new DefaultChallenge(challenge));

DCAttestationRequest dcAttestationRequest = new DCAttestationRequest(keyId, attestationObject, clientDataHash);
DCAttestationParameters dcAttestationParameters = new DCAttestationParameters(dcServerProperty);
DCAttestationData dcAttestationData;
try {
    dcAttestationData = deviceCheckManager.parse(dcAttestationRequest);
} catch (DataConversionException e) {
    // If you would like to handle Apple App Attest data structure parse error, please catch DataConversionException
    throw e;
}
try {
    deviceCheckManager.validate(dcAttestationData, dcAttestationParameters);
} catch (ValidationException e) {
    // If you would like to handle Apple App Attest data validation error, please catch ValidationException
    throw e;
}

// please persist Authenticator object, which will be used in the authentication process.
DCAppleDevice dcAppleDevice =
        new DCAppleDeviceImpl( // You may create your own Authenticator implementation to save friendly authenticator name
                dcAttestationData.getAttestationObject().getAuthenticatorData().getAttestedCredentialData(),
                dcAttestationData.getAttestationObject().getAttestationStatement(),
                dcAttestationData.getAttestationObject().getAuthenticatorData().getSignCount(),
                dcAttestationData.getAttestationObject().getAuthenticatorData().getExtensions()
        );
save(dcAppleDevice); // please persist authenticator in your manner

2.3.3. Apple App Attest assertion verification

To parse and validate an assertion on authentication, call DeviceCheckManager#validate with a DCAssertionRequest instance as an argument. If you would like to access the parsed data when an validation error occurred, please use DeviceCheckManager#parse to parse the authentication request and pass the returned DCAssertionData instance to DeviceCheckManager#validate method.

The members of DCAssertionRequest are the values obtained by the App Attest API in the iOS device. Transmit from the iOS device to the server side in some way.

DCAssertionParameters is an another argument for DeviceCheckManager#parse method, and contains server property, persisted authenticator and validation conditions.

// Client properties
byte[] keyId = null /* set keyId */;
byte[] assertion = null /* set assertion */;
byte[] clientDataHash = null /* set clientDataHash */;

// Server properties
String teamIdentifier = null /* set teamIdentifier */;
String cfBundleIdentifier = null /* set cfBundleIdentifier */;
byte[] challenge = null;
DCServerProperty dcServerProperty = new DCServerProperty(teamIdentifier, cfBundleIdentifier, new DefaultChallenge(challenge));

DCAppleDevice dcAppleDevice = load(keyId); // please load authenticator object persisted in the attestation process in your manner

DCAssertionRequest dcAssertionRequest =
        new DCAssertionRequest(
                keyId,
                assertion,
                clientDataHash
        );
DCAssertionParameters dcAssertionParameters =
        new DCAssertionParameters(
                dcServerProperty,
                dcAppleDevice
        );

DCAssertionData dcAssertionData;
try {
    dcAssertionData = deviceCheckManager.parse(dcAssertionRequest);
} catch (DataConversionException e) {
    // If you would like to handle Apple App Attest data structure parse error, please catch DataConversionException
    throw e;
}
try {
    deviceCheckManager.validate(dcAssertionData, dcAssertionParameters);
} catch (ValidationException e) {
    // If you would like to handle Apple App Attest data validation error, please catch ValidationException
    throw e;
}
// please update the counter of the authenticator record
updateCounter(
        dcAssertionData.getCredentialId(),
        dcAssertionData.getAuthenticatorData().getSignCount()
);

3. Configuration

WebAuthn4J has a one main entry point class, WebAuthnManager. It delegates attestation statements validation to an implementation of AttestationStatementValidator and attestation statements trustworthiness validation to an implementation of CertPathTrustworthinessValidator.

Since most sites don’t require strict attestation statement validation (WebAuthn Spec related topic ), WebAuthn4J provides WebAuthnManager.createNonStrictWebAuthnManager factory method that returns an WebAuthnManager instance configured AttestationStatementValidator and CertPathTrustworthinessValidator not to validate attestation statements.

If you are engaging an enterprise use case and strict authenticator verification is a requirement, Use the constructor of the WebAuthnManager class and inject validators.

3.1. Attestation statement validation

Attestation statement validation is provided by the implementation of AttestationStatementValidator interface. For each attestation statement format, corresponding validator classes are provided. Please specify its list at the first argument of the constructor of WebAuthnManager class. For example, if you would like to limit the supported format to packed only, add only PackedAttestationStatementValidator to the List, and if you would like to support packed and tpm format, make the List with PackedAttestationStatementValidator and TPMAttestationStatementValidator.

3.1.1. Attestation statement trustworthiness validation

Attestation statement trustworthiness validation has two patterns: certificate path validation, and self attestation. Certificate path validation is delegated via CertPathTrustworthinessValidator interface. WebAuthn4J provides DefaultCertPathTrustworthinessValidator as CertPathTrustworthinessValidator implementation. DefaultCertPathTrustworthinessValidator verifies trustworthiness by checking the attestation certificate chains to the root certificate provided as TrustAnchor via TrustAnchorRepository interface.

3.1.2. Trust anchor resolution

TrustAnchorRepository is an interface that resolves TrustAnchor from AAGUID or attestationCertificateKeyIdentifier. webauthn4j-core module provides a KeyStoreTrustAnchorRepository as a TrustAnchorRepository. KeyStoreTrustAnchorRepository fetches TrustAnchor from a Java Key Store. Please note that KeyStoreTrustAnchorRepository does not return a different TrustAnchor depending on AAGUID or attestationCertificateKeyIdentifier. All certificates registered in the Java Key Store file are treated as trust anchors.

Trust anchor resolution using FIDO Metadata Service
webauthn4j-metadata module, which provides FIDO Metadata Statement handling, is under experimental status.

FIDO Alliance offers FIDO Metadata Service, which provides metadata of authenticators. webauthn4j-metadata module provides a MetadataBLOBBasedTrustAnchorRepository as a TrustAnchorRepository implementation. MetadataBLOBBasedTrustAnchorRepository can provide trust anchors based on the information published by FIDO Metadata Service when it is used in combination with FidoMDS3MetadataBLOBProvider.

3.2. What WebAuthn4J doesn’t offer

In order to realize framework independence, WebAuthn4J intentionally scopes functions to WebAuthn Assertion / Attestation verification. Fetching parameters from HTTP request, issuing and saving Challenge in session, counter Value validation are not provided. Please implement in your caller code. If you are using Spring Security, consider using WebAuthn$J Spring Security as it provides these implementations of authentication framework specific parts.

4. Deep-Dive

4.1. Representation of an authenticator

WebAuthn4j provides Authenticator interface as a representation of an authenticator.

On registering the authenticator, you need to persist its representation by creating the instance implementing Authenticator interface in your application manner because it is used afterwards on authentication verification. It might be better to use credentialId as a search key for this persisted instance.

You can freely enhance the class implementing Authenticator interface in order to meet your application’s requirements. For example, you can add the field for your application’s user to identify the authenticator.

4.2. Authenticator serialization and deserialization

While it is application’s responsibility to serialize Authenticator instance at registration, WebAuthn4J provides an utility class to serialize or deserialize members of Authenticator class. Please use them for implementing persistence in your application.

4.2.1. attestedCredentialData

AttestedCredentialDataConverter converts from AttestedCredentialData to byte[] and vice versa. If you would like to persist as String, use Base64UrlUtil to convert from byte[] to base64url String.

AttestedCredentialDataConverter attestedCredentialDataConverter = new AttestedCredentialDataConverter(objectConverter);

// serialize
byte[] serialized = attestedCredentialDataConverter.convert(attestedCredentialData);
// deserialize
AttestedCredentialData deserialized = attestedCredentialDataConverter.convert(serialized);

4.2.2. attestationStatement

Since AttestationStatement is an interface, there are some implementation classes like PackedAttestationStatement or AndroidKeyAttestationStatement per format. As AttestationStatement is not self-descriptive for its format, the format need to be persisted in an another field. Because of that, an envelope class which has attestationStatement field and format field is required, and the envelope class need to be serialized for persisting AttestationStatement.

//serialize
AttestationStatementEnvelope envelope = new AttestationStatementEnvelope(attestationStatement);
byte[] serializedEnvelope = objectConverter.getCborConverter().writeValueAsBytes(envelope);

//deserialize
AttestationStatementEnvelope deserializedEnvelope = objectConverter.getCborConverter().readValue(serializedEnvelope, AttestationStatementEnvelope.class);
AttestationStatement deserializedAttestationStatement = deserializedEnvelope.getAttestationStatement();
class AttestationStatementEnvelope{

    @JsonProperty("attStmt")
    @JsonTypeInfo(
            use = JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
            property = "fmt"
    )
    private AttestationStatement attestationStatement;

    @JsonCreator
    public AttestationStatementEnvelope(@JsonProperty("attStmt") AttestationStatement attestationStatement) {
        this.attestationStatement = attestationStatement;
    }

    @JsonProperty("fmt")
    public String getFormat() {
        return attestationStatement.getFormat();
    }

    public AttestationStatement getAttestationStatement() {
        return attestationStatement;
    }
}

4.2.3. transports

If you would like to persist as JSON String, use ObjectConverter.

String serializedTransports = objectConverter.getJsonConverter().writeValueAsString(transports);

4.2.4. counter

This member is long. Nothing special is required.

4.2.5. authenticatorExtensions

This member can be serialized as CBOR as it is originally CBOR data.

byte[] serializedAuthenticatorExtensions = objectConverter.getCborConverter().writeValueAsBytes(authenticatorExtensions);

4.2.6. clientExtensions

This member can be serialized as JSON as it is originally JSON data.

String serializedClientExtensions = objectConverter.getJsonConverter().writeValueAsString(clientExtensions);

4.3. DCAppleDevice serialization and deserialization

When you use webauthn4j-device-check, you need to persist DCAppleDevice instead of Authenticator interface between attestation and assertion. In general, you can serialize and deserialize it by the method explained in Authenticator serialization and deserialization, but ObjectConverter must be the one with DeviceCheckCBORModule registered. A ObjectConverter with a DeviceCheckCBORModule can be obtained with DeviceCheckManager.createObjectConverter static method.

4.4. Using FIDO CTAP2 Security key in your own application other than WebAuthn

For FIDO CTAP2 Security key, WebAuthn is just an application. An original application can use a a security key too. This section describes how to use WebAuthn4J for attestation and assertion validation in your own application using the FIDO CTAP2 security key.

4.4.1. Registration & Authentication flow of your own application using FIDO CTAP2 security key

If you use FIDO CTAP2 security key for authentication in your own application, you need to register the security key first. Call the authenticatorMakeCredential method of the security key to retrieve the "Attestation" data, which contains public key and device configuration and save it. The obtained attestation data need to be validated to determine if the security key is acceptable for the application. WebAuthn4J can validate the attestation with CoreRegistrationValidator class. For authentication, the application need to call the authenticatorGetAssertion method of the security key to retrieve the "assertion" data, which contains signature. By validating the retrieved assertion, the application can determine whether the security key used for authentication is the same as the one used for registration, and can determine whether the access is legitimate. WebAuthn4J can validate the assertion with CoreAuthenticationValidator class.

4.4.2. How to validate application specific client data

Implementing the above flow will provide authentication feature, but if the entity that calls the FIDO CTAP2 security key (client) and the entity that validates the attestation and the assertion are separated, in some cases, an application specific client data is needed to be validated at the server at registration and authentication. The client data itself can be sent together with the attestation and assertion, but in order to protect the client data from MITM attacks, it need to be signed and protected. In FIDO CTAP2 specification, there is a parameter named clientDataHash that is common to authenticatorMakeCredential method used at registration and authenticatorGetAssertion method used at authentication. Since the security key generates a signature from data that contains clientDataHash, an application can validate its specific client data by setting clientDataHash to the hash of the client data and validating the signature.

4.5. Project Modules

WebAuthn4J consists of the following four modules.

4.5.1. Core webauthn4j-core.jar

Provides core features for WebAuthn attestation and assertion verification.

4.5.2. Core webauthn4j-device-check.jar

Provides core features for Apple App Attest attestation and assertion verification.

4.5.3. Metadata webauthn4j-metadata.jar

Provides additional features regarding FIDO Metadata Service. As FIDO Metadata Statement specification is still draft, it is in experimental status. The included classes don’t follow semantic versioning and the design may be changed even though it is public.

4.5.4. Test webauthn4j-test.jar

Internal library for WebAuthn4J testing. The included classes don’t follow semantic versioning and the design may be changed even though it is public.

4.5.5. Util webauthn4j-util.jar

Contains utility classes used in WebAuthn4J library.

4.6. Custom converter implementation

WebAuthn4J uses Jackson library for JSON and CBOR serialization and deserialization. If you would like to custom serialization or deserialization, register custom serializer or deserializer to the underlying Jackson ObjectMapper.

4.6.1. Custom converter registration

Since WebAuthn4J wraps ObjectMapper with ObjectConverter, inject your customized ObjectMapper through ObjectConverter constructor and specify the ObjectConverter instance to the WebAuthnManager instance creation parameter.

4.7. Custom validator implementation

WebAuthn4J can add custom validator. For registration validation, implement CustomRegistrationValidator. For authentication validation, implement CustomAuthenticationValidator.

4.7.1. Custom validator registration

CustomRegistrationValidator and CustomAuthenticationValidator implementation can be registered to WebAuthnManager via its constructor’s customRegistrationValidators and customAuthenticationValidators parameters.

4.8. Classes

4.8.1. Data Transfer Objects

Classes under com.webauthn4j.data package are designed as immutable DTO.

4.8.2. Converter, Jackson Modules for WebAuthn

Classes under com.webauthn4j.data package are designed as being serializable and deserializable.

Some Classes under converter package needs custom serializer and deserializer. Jackson’s module named WebAuthnJSONModule and WebAuthnCBORModule consolidate these custom serializer and deserializer. WebAuthn4J’s validators register these modules onto Jackson’s ObjectMapper automatically.

If you want to use WebAuthn4J’s serializer and deserializer outside of WebAuthnManager, you can register these modules onto Jackson’s ObjectMapper.

4.8.3. TrustAnchorsResolver

TrustAnchorsResolver interface is used by TrustAnchorCertPathTrustworthinessValidator to explore root certificates in the verification of the authenticity of the attestation statements.

4.8.4. TrustAnchorsProvider

TrustAnchorsProvider is an interface that TrustAnchorsResolverImpl delegates TrustAnchor load operation to. KeyStoreFileTrustAnchorsProvider is provided as an implementation for loading TrustAnchor from Java Key Store file. WebAuthn$J Spring Security also provides CertFileResourcesTrustAnchorProvider to load TrustAnchor from Spring Resource.

4.8.5. Exceptions

If some verification fails, WebAuthn4J throws an exception class inheriting ValidationException.

4.9. Logging

WebAuthn4J uses SLF4J as log interface library. You can use any kind of this implementation like Logback as you want.

5. Known Issues

WebAuthn is state of the art technology. Therefore, there are still some limitations products utilizing it have. This section tells you issues known at the time of Dec 2019 that might be encountered if you try to use products utilizing WebAuthn technology.

5.1. Android’s resident key support

Android’s FIDO API has not supported resident key yet. It means Chrome for Android and Firefox for Android don’t provide username-less authentication experience at the time of authentication.

You can mimic such the experience by storing credentialId onto LocalStorage on registration and read it on authentication. However, it doesn’t works if a user uses Roaming Authenticator for authentication.

5.2. Firefox

Firefox supports CTAP2 Authenticators by using OS standard WebAuthn API from Windows 10 1903. However, on macOS and Linux, only FIDO U2F authenticators are supported.

5.3. Implementation differences between browsers

Browser vendors has been struggling to support WebAuthn. However, it is nothing unusual that the feature supported by some platforms are not supported by other platforms. Considering it, it is recommended to prepare some fallback mechanism for your users using platforms not supporting the feature you expected to support.

For example, if you utilize ResidentKey to omit entering user account’s ID, it might be better to support two-factor authentication requiring password authentication for users using Chrome and Firefox, and two-factor authentication without WebAuthn for users using browsers without WebAuthn support.

5.4. WebAuthn4J’s ECDAA support

WebAuthn4J has not yet supported ECDAA (Elliptic Curve based Direct Anonymous Attestation) algorithm. There is no plan to support it for the time being because ECDAA algorithm support is optional by WebAuthn specification and major browsers have not yet supported it. From WebAuthn Level2 specification, ECDAA is dropped.

5.5. WebAuthn4J’s PS256/PS384/PS512 support

WebAuthn4J has not yet supported PS256, PS384, and PS512 algorithms. There is no plan to support it for the time being because these algorithms support is optional by WebAuthn specification and standard Providers of Java Crypto API have not yet supported it.