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.
This commit is contained in:
70
docs/passkeys/README.md
Normal file
70
docs/passkeys/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user