# Passkeys and WebAuthn with Spring Security 7.1 Companion documentation for [Passkeys and WebAuthn with Spring Security 7](https://ankurm.com/passkeys-webauthn-spring-security-7/) on ankurm.com, and for the code in [`passkeys/`](../../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](04-virtual-authenticator.md) 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`](../output) is real program output, regenerated by [`passkeys/scripts/run-all.sh`](../../passkeys/scripts/run-all.sh). ## Chapters | # | chapter | what it settles | |---|---|---| | 01 | [Versions, artifacts and the 7.0 split](01-versions.md) | the dependency `spring-boot-starter-security` does not give you | | 02 | [The minimum configuration](02-minimum-configuration.md) | six endpoints from one DSL block, and the bean that silently disables it | | 03 | [The two ceremonies](03-the-two-ceremonies.md) | what is on the wire, and what every default in the options object means | | 04 | [A software authenticator](04-virtual-authenticator.md) | how to execute a passkey ceremony in CI, with no browser | | 05 | [The defaults](05-defaults.md) | user verification is optional, and asking for attestation is not checking it | | 06 | [The bootstrap problem](06-the-bootstrap-problem.md) | a passkey cannot be a user's first credential | | 07 | [Failure modes](07-failure-modes.md) | why every registration failure is a 500 and every login failure is a bare 401 | | 08 | [The one-time-token fallback](08-one-time-token-fallback.md) | the way in, the way back, and the rate limit that does not exist | | 09 | [Persistence](09-persistence.md) | the in-memory default, the missing DDL, and the column you must not drop | | 10 | [The signature counter](10-signature-counter.md) | stored on every login, compared against on none | | 11 | [Should you build this](11-should-you.md) | the honest answer, and what the afternoon actually costs | ## Captured output | file | produced by | shows | |---|---|---| | [`pk-ceremony.txt`](../output/pk-ceremony.txt) | `scripts/ceremony.sh` | registration and authentication, end to end | | [`pk-counter.txt`](../output/pk-counter.txt) | `scripts/counter.sh` | a stale signature counter being accepted | | [`pk-user-verification.txt`](../output/pk-user-verification.txt) | `scripts/user-verification.sh` | `preferred` versus `required`, same authenticator | | [`pk-origin.txt`](../output/pk-origin.txt) | `scripts/origin.sh` | the phishing defence, in both ceremonies | | [`pk-attestation.txt`](../output/pk-attestation.txt) | `scripts/attestation.sh` | `direct` requested, `none` accepted | | [`pk-duplicate.txt`](../output/pk-duplicate.txt) | `scripts/duplicate.sh` | `excludeCredentials`, and a client that ignores it | | [`pk-bootstrap.txt`](../output/pk-bootstrap.txt) | `scripts/bootstrap.sh` | registration options with nobody logged in | | [`pk-step-up.txt`](../output/pk-step-up.txt) | `scripts/step-up.sh` | `FACTOR_WEBAUTHN` versus `FACTOR_OTT` on one endpoint | | [`pk-ott.txt`](../output/pk-ott.txt) | `scripts/ott-fallback.sh` | generate, redeem, and redeem again | | [`pk-jdbc.txt`](../output/pk-jdbc.txt) | `scripts/jdbc.sh` | the same ceremony against H2 | | [`pk-filters.txt`](../output/pk-filters.txt) | `scripts/filters.sh` | the live filter chain, all 25 of it | | [`pk-test-run.txt`](../output/pk-test-run.txt) | `scripts/test-run.sh` | seven contract tests | ## Quickstart ```bash 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 ```