1. Deep-Dive

1.1. Representation of a credential record

WebAuthn4j provides CredentialRecord interface as a representation of a credential record.

On registering the credential, you need to persist its representation by creating the instance implementing CredentialRecord 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 CredentialRecord interface in order to meet your application’s requirements. For example, you can add a field like name to identify the credential.

1.2. CredentialRecord serialization and deserialization

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

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

1.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. Since the envelope class itself is not provided by the WebAuthn4J library, please implement your own envelope class on the application side, referring to the example below.

//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;
    }
}

1.2.3. transports

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

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

1.2.4. counter

This member is long. Nothing special is required.

1.2.5. authenticatorExtensions

This member can be serialized as CBOR bytes array as it is originally CBOR data. If you would like to persist as String, use Base64UrlUtil to convert from byte[] to base64url String.

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

1.2.6. clientExtensions

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

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

1.3. DCAppleDevice serialization and deserialization

When you use webauthn4j-appattest, you need to persist DCAppleDevice instead of CredentialRecord interface between attestation and assertion. In general, you can serialize and deserialize it by the method explained in CredentialRecord 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.

1.4. Alternative to Unsupported JSON Serialization APIs in Safari

In the Quick Start, PublicKeyCredential.parseCreationOptionsFromJSON is introduced as an API for parsing PublicKeyCredentialCreationOptions, and PublicKeyCredential#toJSON as an API for serializing PublicKeyCredential. However, as of today(December 2024), these APIs are not available in Safari.

As an alternative, it is recommended to use the pony-fill provided by the npm library github/@webauthn-json, maintained by GitHub. It provides parseCreationOptionsFromJSON method as a substitute for PublicKeyCredential.parseCreationOptionsFromJSON and create as a substitute for navigator.credentials.create.

Example 1: github/@webauthn-json: Creating a credential
import {
  create,
  parseCreationOptionsFromJSON,
} from "@github/webauthn-json/browser-ponyfill";

const response = await fetch("<endpoint path that returns PublicKeyCredentialCreationOptions as JSON>") //fetch PublicKeyCredentialCreationOptions as JSON string
const publicKeyCredentialCreationOptionsJSON = await response.json() // convert to JSONObject
const credentialCreationOptions = parseCreationOptionsFromJSON(publicKeyCredentialCreationOptionsJSON); // convert to PublicKeyCredentialCreationOptions
const publicKeyCredential = await create({ publicKey: credentialCreationOptions}); // create PublicKeyCredential
const registrationResponseJSON = publicKeyCredential.toJSON() // JSON object of publicKeyCredential
const registrationResponseJSONStr = JSON.stringify(registrationResponseJSON) // JSON string representation of publicKeyCredential

The toJSON method can be used on the publicKeyCredential obtained using the create method of this pony-fill.

It also provides parseRequestOptionsFromJSON as a substitute for PublicKeyCredential.parseRequestOptionsFromJSON, and get is provided as a substitute for navigator.credentials.get.

Example 2: github/@webauthn-json: Getting a credential
import {
  get,
  parseRequestOptionsFromJSON,
} from "@github/webauthn-json/browser-ponyfill";

const response = await fetch("<endpoint path that returns PublicKeyCredentialRequestOptions as JSON>");
const publicKeyCredentialRequestOptionsJSON = await response.json();
const credentialGetOptions = parseRequestOptionsFromJSON(publicKeyCredentialRequestOptionsJSON);
const publicKeyCredential = await get({ publicKey: credentialGetOptions });
const authenticationResponseJSON = publicKeyCredential.toJSON();
const authenticationResponseJSONStr = JSON.stringify(authenticationResponseJSON);

1.5. Modules

WebAuthn4J consists of the following four modules.

1.5.1. Core: webauthn4j-core.jar

Provides core features for WebAuthn attestation and assertion verification.

1.5.2. Metadata: webauthn4j-metadata.jar

Provides additional features regarding FIDO Metadata Service.

1.5.3. Core-Async: webauthn4j-core-async.jar

Provides async variant of core features for WebAuthn attestation and assertion verification.

1.5.4. Metadata-Async: webauthn4j-metadata-async.jar

Provides async variant of additional features regarding FIDO Metadata Service.

1.5.5. App Attest: webauthn4j-appattest.jar

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

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

1.5.7. Util: webauthn4j-util.jar

Contains utility classes used in WebAuthn4J library.

1.6. Custom validator implementation

WebAuthn4J can add custom validator. For registration validation, implement CustomRegistrationVerifier. For authentication validation, implement CustomAuthenticationVerifier.

1.6.1. Custom validator registration

CustomRegistrationVerifier and CustomAuthenticationVerifier implementation can be registered to WebAuthnManager via its constructor’s customRegistrationVerifiers and customAuthenticationVerifiers parameters.

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

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

1.8. Classes

1.8.1. Data Transfer Objects

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

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

1.8.3. TrustAnchorsResolver

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

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

1.8.5. Exceptions

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

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

1.9.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 verified to determine if the security key is acceptable for the application. WebAuthn4J can verify the attestation with CoreRegistrationVerifier 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 verify the assertion with CoreAuthenticationVerifier class.

1.9.2. How to verify 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 verifies the attestation and the assertion are separated, in some cases, an application specific client data is needed to be verified 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 verify its specific client data by setting clientDataHash to the hash of the client data and validating the signature.