# Passkey Authentication Service

**Solution Architecture v1.0 · open source, self-hosted on Kubernetes · Security & Identity Architecture · 2026-10 · 21 views · 15 architecture decision records**

Every major consumer service now offers to sign you in with a passkey, and the cryptography behind that button is settled: WebAuthn specifies the ceremony, the private key never leaves the authenticator, and a phishing site cannot produce a valid assertion for a domain it does not control. None of that is the hard part. The hard part is everything the specification deliberately leaves to the relying party, and it is where passwordless deployments actually fail — where the five-minute challenge lives and what consumes it exactly once, what happens to the user who has one passkey and drops their phone in a lake in a system with no password to reset, whether a signature counter that moved backwards is a cloned authenticator or a synced passkey behaving exactly as its vendor intended, and when a platform gets to say the password is gone. This package is the architecture for a multi-tenant service that answers those questions for forty relying parties: ceremony handling, credential lifecycle and device management, account recovery, authenticator trust admission, abuse controls, and the migration that ends with the password switched off. The assumed operating context is 40 relying-party tenants and 180 million end-user accounts averaging 2.3 credentials each, 20,000 authentication ceremonies per second per region with a 5× burst for 120 seconds, 400 million credential records in roughly 480 GB, and 2.5 billion authentication events a year — on self-hosted Kubernetes in two owned regions with Redis for region-pinned ceremony state, CockroachDB with regional-by-row placement for credentials, a network HSM behind Vault for key custody, Envoy at the edge, and Kafka into ClickHouse and MinIO for evidence.

The product is not the passkey. It is **a login that cannot be phished and an account that can still be recovered** — and the second half is what rearranges every assumption the first half invites.

The design rests on one boundary: **ceremony state is ephemeral and region-pinned; credential state is durable and globally replicated; the authentication hot path touches nothing else.** That single line is why a 60 ms p99 needs no distributed transaction in a login, why a regional failure costs a retry rather than a wrong decision, why every dependency has a declared degradation instead of an outage, and why the backup budget collapses onto one store out of five.

The decisions that carry the design:

- **Ceremony state and credential state are split by lifetime, not subject.** One retry's worth of loss on one side, a locked-out human on the other, so each gets the guarantee it actually needs (ADR-01).
- **A challenge is consumed in a store, not carried as a token.** A signature proves issuance; only a record proves use, and a stateless challenge needs a nonce table to be safe (ADR-02).
- **Tenant policy is read from replica memory, never from the control plane.** It is the only way logins survive a total management-plane outage (ADR-03).
- **Authenticator trust is admitted once and recorded immutably.** Attestation is not resent with an assertion, so evaluating it per login puts an external feed on the hot path and makes a credential's fate depend on publication timing (ADR-04).
- **The metadata snapshot is signature-verified and fails static.** Tolerate unavailable generously; refuse untrusted absolutely (ADR-05).
- **The sign counter is recorded and alerted on, enforced only by tenant choice.** Synced passkeys legitimately report zero, and strict enforcement locks out far more people than it catches (ADR-06).
- **Fail closed on identity, open on advice, static on reference data.** Three different questions, three different answers; a platform that gives one answer to all three is wrong twice (ADR-07).
- **Shed challenge issuance before assertion verification.** A user who has already touched their sensor has done work the platform paid for (ADR-08).
- **In-flight ceremonies are abandoned on failover, never migrated.** Replicating disposable state makes single-use consumption probabilistic during exactly the conditions nobody can reason about (ADR-09).
- **Every recovery route is classified by assurance and must end in a credential.** An account protected by a passkey and recoverable by an emailed link is an account protected by an emailed link (ADR-10).
- **A recovery-enrolled credential waits before it may revoke.** Recovery and revocation compose into an eviction attack; the attacker needs it immediate and the honest owner almost never does (ADR-11).
- **The platform issues verdicts; the relying party issues sessions.** Producing evidence is cheaper to be wrong about, and far cheaper to be breached with, than producing authority (ADR-12).
- **Revocation is a state change; a policy change is never retroactive.** Share a mechanism between them and the quieter one eventually does the louder one's damage (ADR-13).
- **One tenant-owned RP ID, with related origins for brands.** A convenience RP ID on the platform's domain is a migration the tenant cannot perform (ADR-14).
- **The cutover is gated on measured two-or-more coverage.** Do not block the decision; block it being made without the number (ADR-15).

The architecture one-pager (including why the design should still hold up in ten years, what a four-week prototype should falsify, and the six risks that would change it) and the full decision record appear on the landing page of the diagram set, directly below the index of views. The same content is published as [docs/architecture-one-pager.md](docs/architecture-one-pager.md) (~15 min) and [docs/decision-record.md](docs/decision-record.md) (~67 min).

---

## What is here

| Path | Contents |
|---|---|
| `diagrams/index.html` | The landing page: 21 views in seven acts with every format linked, then the **architecture one-pager** and the **decision record** |
| `diagrams/*.html` | One self-contained page per view: the inlined diagram plus the reasoning cards, with copy / PNG / PDF export |
| `diagrams/svg/*.svg` | The same views as SVG with the diagram XML embedded; they re-open fully editable in diagrams.net |
| `diagrams/drawio/*.drawio` | draw.io source |
| `docs/architecture-one-pager.md` | The one-pager as markdown |
| `docs/decision-record.md` | The 15 decision records, the capability-to-technology table and the package glossary as markdown |
| `specs/part-a..c.json` | Diagram specifications, the source of truth for every view |
| `specs/manifest-a..b.json` | Acts, page titles, subtitles and reasoning cards |
| `specs/adr-onepager.json`, `specs/adr-records-a..b.json` | The one-pager, the decision records, the capability-to-technology table and the glossary |
| `scripts/build.sh` | Rebuilds every deliverable from the specs (Node only, no network) |
| `ask.md` | The requirement |

## The twenty-one views

| # | View | What it answers |
|---|---|---|
| 01 | System Context | Who signs in, which trust inputs arrive, and the three relationships refused on purpose — authorization, password storage and session issuance |
| 02 | High-Level Architecture | Seven stages, with the shed gate before the ceremony plane rather than inside it |
| 03 | Actors and Their Journeys | Eight parties, three of them machines, including an authenticator whose goal is a constraint on the platform |
| 04 | Journey — Enrolling a First Passkey | The trough is not the biometric; it is the unanswered question about losing the device |
| 05 | Journey — Losing Every Device | The journey that starts in the trough, where the architecture has to do the climbing |
| 06 | Journey — Turning Off the Password | The trough is the platform refusing the thing the administrator came to do |
| 07 | Layered Architecture | Eight layers, of which exactly one holds durable truth, and it is read-only from the hot path |
| 08 | Platform Components | What is stateless, what is region-pinned, what is globally replicated, and what holds a key |
| 09 | Interface Catalogue | Three inbound interfaces, three outbound, none of the outbound three on the login path |
| 10 | Data Flow | Where client data and authenticator data go, and the single point where personal data stops |
| 11 | Storage Zones by Rebuildability | Five zones, four of which can be lost and rebuilt, and the 480 GB that cannot |
| 12 | Data Model | Nine entities, and the one that is immutable on purpose |
| 13 | Critical Flow — One Authentication Ceremony | Seventeen messages, of which one is a timeout and is still the designed behaviour |
| 14 | Registration and Admission Pipeline | Seven stages ending in an immutable verdict, or in nothing at all |
| 15 | Recovery Routes and Their Assurance | Four routes across five stages, and the cool-off column that carries the argument |
| 16 | Deployment Topology | Two regions, one global credential cluster, and two ceremony stores that never speak to each other |
| 17 | Code and Policy Release Pipeline | One pipeline for both, and the gate that counts the users a change would lock out |
| 18 | Observability Matrix | Five signal types across five stages, with the blank cells meant literally |
| 19 | The Credential Lifecycle Loop | Seven states, and a loop that closes through replacement rather than revocation |
| 20 | Trust Zones and Segmentation | Six zones ordered by exposure, and the property the ordering exists to preserve |
| 21 | Identity Flow — Verdict to Session | Nine messages, and the two that deliberately do not exist |

## Rebuilding

```bash
bash scripts/build.sh
```

Node 20+ and nothing else. No draw.io Desktop, no browser, no network. The build generates the
draw.io sources from `specs/part-*.json`, fails on geometry or routing defects, renders both SVG
flavours, writes the HTML pages and the index, injects the one-pager and decision record below the
index, and proves every relative link resolves. The current set passes both gates with **0 errors
and 0 warnings** across 157 routed edges, with 320 icons embedded and all 292 nodes resolved.
