Skip to content
ecdsa.com
Java · Kotlin

WebAuthn4J

WebAuthn4J, the WebAuthn verification library behind Spring Security's passkey support and Keycloak: no hidden defaults for user verification, DER-only signatures, and a hard failure on counter regressions.
License
Apache-2.0
Checked version
0.31.11.RELEASE ·
Source
github.com/webauthn4j/webauthn4j
Documentation
webauthn4j.github.io/webauthn4j/en/

Compatibility at a glance

WebAuthn

WebAuthn4J verifies registration and authentication responses — attestation statements of every format, COSE keys, flags, counters and signatures — and leaves building the options to the relying party. Spring Security and Keycloak use it underneath. 0.31.11 depends on Jackson 3 (tools.jackson.*), which matters when your application still uses Jackson 2's com.fasterxml.jackson for its own JSON, and its API carries JetBrains @NotNull annotations: compiling against it with the single-file java launcher failed until org.jetbrains:annotations was on the classpath.

There is no default user-verification policy to forget: RegistrationParameters and AuthenticationParameters take userVerificationRequired and userPresenceRequired as mandatory arguments. In the check, a registration and an assertion with the UV flag clear verified when false was passed and failed with UserNotVerifiedException when true was. Keep the value consistent with the userVerification you send in the options, as the sample shows.

Signatures must be ASN.1 DER: a raw 64-byte r‖s assertion signature failed with BadSignatureException: Assertion signature is not valid., and a high-S DER signature verified. A counter lower than the stored one fails the login with MaliciousCounterValueException: Malicious counter value is detected. Cloned authenticators exist in parallel., while a counter that has always been 0, as synced passkeys report, passes. Ed25519 credentials verified end to end.

createNonStrictWebAuthnManager() accepts any attestation statement without checking it against trust anchors, which is right when you ask for attestation none and wrong when attestation is a policy decision; configure trust anchors and use a strict manager in that case.

The WebAuthn inspector decodes what browsers send, and AAGUID lookup identifies authenticator models for attestation policies.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/webauthn4j/run.sh ran WebAuthn4J 0.31.11.RELEASE in the container image maven:3.9.16-eclipse-temurin-25 on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • User verification argument

    mandatory

    userVerificationRequired and userPresenceRequired are required constructor arguments; with false, responses without the UV flag verified, with true they failed with UserNotVerifiedException.

    Recorded by the check
    fact uv-requirement-argument = mandatory
    evidence uv-requirement-argument: registration with UV clear, userVerificationRequired false: verified; true: UserNotVerifiedException: Verifier is configured to check user verified, but UV flag in authenticatorData is not set.
    evidence uv-requirement-argument: RegistrationParameters and AuthenticationParameters take userVerificationRequired and userPresenceRequired as required booleans
  • Assertion signature encoding

    der

    A correct ES256 assertion with a DER signature verified.

    Recorded by the check
    fact webauthn-assertion-encoding = der
  • Raw r‖s assertion signature

    rejected

    A raw 64-byte r‖s signature failed with BadSignatureException: Assertion signature is not valid.

    Recorded by the check
    fact webauthn-raw-signature = rejected
  • High-S assertion signature

    accepted

    A DER signature with s replaced by n − s verified.

    Recorded by the check
    fact webauthn-high-s = accepted
  • Signature counter going back

    rejected

    A counter of 3 after a stored 5 failed with MaliciousCounterValueException; 0 after a stored 0 verified.

    Recorded by the check
    fact webauthn-counter-regression = rejected
  • Ed25519 credentials

    supported

    An Ed25519 (COSE -8) credential registered and authenticated successfully.

    Recorded by the check
    fact webauthn-ed25519 = supported
    evidence webauthn-ed25519: verified

Usage

Create a verification manager and registration parameters that state the user-verification and user-presence requirements explicitly.
import com.webauthn4j.WebAuthnManager;
import com.webauthn4j.data.RegistrationParameters;
import com.webauthn4j.data.PublicKeyCredentialParameters;
import com.webauthn4j.data.PublicKeyCredentialType;
import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier;
import com.webauthn4j.data.client.Origin;
import com.webauthn4j.data.client.challenge.DefaultChallenge;
import com.webauthn4j.server.ServerProperty;
import java.util.List;

public class Usage {
    public static void main(String[] args) {
        // "Non-strict" accepts every attestation format without trust anchors; pair it
        // with attestation "none" in the options, or configure trust anchors instead.
        WebAuthnManager manager = WebAuthnManager.createNonStrictWebAuthnManager();
        ServerProperty server = new ServerProperty(new Origin("https://example.com"), "example.com", new DefaultChallenge());

        // There is no default: every verification states UV and UP requirements explicitly.
        RegistrationParameters registration = new RegistrationParameters(server,
            List.of(new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256)),
            true, true);
        // manager.verify(manager.parseRegistrationResponseJSON(json), registration) does the rest.
        System.out.println("uv required: " + registration.isUserVerificationRequired()
            + ", up required: " + registration.isUserPresenceRequired() + ", manager: " + manager.getClass().getSimpleName());
    }
}

The snippet harness runs every sample verbatim in the container image maven:3.9.16-eclipse-temurin-25, and the run fails unless the output matches.

Setup
mvn -q -B dependency:copy-dependencies -DoutputDirectory=lib >/dev/null
Command
java -cp 'lib/*' Usage.java
Output must match (regular expression)
uv required: true, up required: true, manager: WebAuthnManager

Libraries