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.
71 lines
4.4 KiB
Markdown
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
|
|
```
|