Fourth Maven project in the repository. Registration and authentication run end to end with no browser and no hardware key: VirtualAuthenticator emits real CBOR attestation objects and real ES256 assertion signatures, and tools/PasskeyCeremony.java drives the live HTTP endpoints with them. Profiles cover userVerification REQUIRED, DIRECT attestation, a disallowed origin and JDBC persistence. Eleven doc chapters and twelve captured transcripts under docs/passkeys and docs/output/pk-*.txt, all regenerated by passkeys/scripts/run-all.sh.
4.4 KiB
4.4 KiB
Passkeys and WebAuthn with Spring Security 7.1
Companion documentation for
Passkeys and WebAuthn with Spring Security 7
on ankurm.com, and for the code in passkeys/.
The other three projects in this repository move bearer tokens around. This one gets rid of the password — and then spends most of its length on the parts that are not the ceremony, because the ceremony is the easy half.
Everything here was run. There is no browser and no hardware key anywhere in this module: a software authenticator produces genuine CBOR attestation objects and genuine ES256 assertion signatures, and Spring Security verifies them without noticing.
| JDK | Temurin 25.0.4.1+1 (current LTS) |
| Spring Boot | 4.1.1 |
| Spring Framework | 7.0.9 |
| Spring Security | 7.1.1 (GA 20 August 2026) |
spring-security-webauthn |
7.1.1 — a separate artifact since 7.0 |
| WebAuthn4J | 0.31.9.RELEASE |
| Jackson | 3.1.5 (tools.jackson) |
| Tomcat | 11.0.24 |
| Maven | 3.9.11 |
Every file in docs/output/pk-*.txt is real program output, regenerated by
passkeys/scripts/run-all.sh.
Chapters
| # | chapter | what it settles |
|---|---|---|
| 01 | Versions, artifacts and the 7.0 split | the dependency spring-boot-starter-security does not give you |
| 02 | The minimum configuration | six endpoints from one DSL block, and the bean that silently disables it |
| 03 | The two ceremonies | what is on the wire, and what every default in the options object means |
| 04 | A software authenticator | how to execute a passkey ceremony in CI, with no browser |
| 05 | The defaults | user verification is optional, and asking for attestation is not checking it |
| 06 | The bootstrap problem | a passkey cannot be a user's first credential |
| 07 | Failure modes | why every registration failure is a 500 and every login failure is a bare 401 |
| 08 | The one-time-token fallback | the way in, the way back, and the rate limit that does not exist |
| 09 | Persistence | the in-memory default, the missing DDL, and the column you must not drop |
| 10 | The signature counter | stored on every login, compared against on none |
| 11 | Should you build this | the honest answer, and what the afternoon actually costs |
Captured output
| file | produced by | shows |
|---|---|---|
pk-ceremony.txt |
scripts/ceremony.sh |
registration and authentication, end to end |
pk-counter.txt |
scripts/counter.sh |
a stale signature counter being accepted |
pk-user-verification.txt |
scripts/user-verification.sh |
preferred versus required, same authenticator |
pk-origin.txt |
scripts/origin.sh |
the phishing defence, in both ceremonies |
pk-attestation.txt |
scripts/attestation.sh |
direct requested, none accepted |
pk-duplicate.txt |
scripts/duplicate.sh |
excludeCredentials, and a client that ignores it |
pk-bootstrap.txt |
scripts/bootstrap.sh |
registration options with nobody logged in |
pk-step-up.txt |
scripts/step-up.sh |
FACTOR_WEBAUTHN versus FACTOR_OTT on one endpoint |
pk-ott.txt |
scripts/ott-fallback.sh |
generate, redeem, and redeem again |
pk-jdbc.txt |
scripts/jdbc.sh |
the same ceremony against H2 |
pk-filters.txt |
scripts/filters.sh |
the live filter chain, all 25 of it |
pk-test-run.txt |
scripts/test-run.sh |
seven contract tests |
Quickstart
cd spring-auth-demo/passkeys
./scripts/run.sh # http://localhost:8080/login, user/password
./scripts/ceremony.sh # both ceremonies, no browser
./scripts/run-all.sh # regenerate every docs/output/pk-*.txt