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.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.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
.
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
.
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.6. Custom validator implementation
WebAuthn4J can add custom validator.
For registration validation, implement CustomRegistrationVerifier
.
For authentication validation, implement CustomAuthenticationVerifier
.
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.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.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.