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, these APIs are not available on Safari versions earlier than 18.4.

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

In WebAuthn, “Origin verification” means the server verifies that the Origin recognized by the user agent (browser) at the time of the registration or authentication ceremony matches the Origin expected by the RP. This verification relies on the origin recorded in clientData and ensures the ceremony actually started from the intended page, preventing phishing and unintended cross‑origin invocation.

By default, WebAuthn does not allow cross‑origin usage within iframes (e.g., executing WebAuthn inside an iframe whose parent page is on a different origin). This is to uphold the same‑origin policy and mitigate threats such as clickjacking, or forcing authentication from another origin.

That said, there are legitimate use cases where you may want to allow cross‑origin execution in a constrained manner: Embedding an authentication widget in a partner site (different parent and RP origins).

In such cases, you should both declare permission to the client side via Permissions‑Policy directives and also configure server‑side verification appropriately. WebAuthn4J provides a ServerProperty builder API to declare acceptable origins for the RP page, and additionally to constrain acceptable top origins (the top‑level document/parent page). By default, cross‑origin usage is not allowed; you must explicitly opt in by configuring acceptable top origins.

1.6.1. origin verification

  • How to specify

    • origin(…​), origins(…​), originPredicate(…​): Specify the acceptable origin(s) for the RP page.

  • Default behavior

    • Only requests whose clientData.origin matches the specified condition are accepted.

  • Example

ServerProperty serverProperty = ServerProperty.builder()
    .origin(new Origin("https://rp.example.com"))
    .rpId("rp.example.com")
    .challenge(challenge)
    .build();

1.6.2. topOrigin verification

  • Purpose

    • When running WebAuthn in an iframe, verify the top‑level document’s origin (the parent page) and allow the ceremony only when it is framed by the expected parent.

  • How to specify

    • topOrigin(…​), topOrigins(…​), topOriginPredicate(…​): Allow only the specified top origins

    • anyTopOrigin(): Allow any top origin (convenient, but expands the security boundary and should be used with care)

  • Default behavior

    • If you do not configure a topOriginPredicate, cross‑origin iframe usage is not allowed.

    • If the client does not provide topOrigin, only the normal origin verification is performed.

  • Example (allow a specific parent origin)

ServerProperty serverProperty = ServerProperty.builder()
    .origin(new Origin("https://rp.example.com"))
    .rpId("rp.example.com")
    .challenge(challenge)
    .topOrigin(new Origin("https://parent.example.com"))
    .build();
  • Example (allow any parent origin — not recommended for production)

ServerProperty serverProperty = ServerProperty.builder()
    .origin(new Origin("https://rp.example.com"))
    .rpId("rp.example.com")
    .challenge(challenge)
    .anyTopOrigin()
    .build();
  • Security considerations

    • anyTopOrigin significantly increases the attack surface. Prefer whitelisting specific parent origins with topOrigin/topOrigins/topOriginPredicate wherever possible.

  • Backward compatibility

    • Legacy coarse‑grained allow flags may remain for compatibility, but for new setups use topOriginPredicate (or anyTopOrigin where strictly necessary). The client’s crossOrigin value is a supplemental signal; the final allow/deny decision should be made by the server‑side topOrigin configuration.

1.7. Custom validator implementation

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

1.7.1. Custom validator registration

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

1.8. 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.8.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.9. Classes

1.9.1. Data Transfer Objects

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

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

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

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

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

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