1
0
Files
spring-auth-demo/docs/passkeys/README.md
Ankur Mhatre f6dd692177 Add passkeys project: WebAuthn ceremonies, a software authenticator and the one-time-token fallback
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.
2026-08-25 23:00:27 +05:30

71 lines
4.4 KiB
Markdown

# 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
```