auth: publish passkey foundations preview
verify / verify (push) Successful in 3m40s

This commit is contained in:
2026-08-21 17:33:00 -04:00
parent fb6bbd0dad
commit bfe6cfd29e
230 changed files with 44547 additions and 17 deletions
+8
View File
@@ -9,6 +9,8 @@ analytics ──> requestlog ──> requestmeta
abuse ─────────────────────> requestmeta
authhttp ──> websec ───────> requestmeta
authhttp ──> auth <───────── authsqlite
│ ▲ ▲
└──> authwebauthn ───────────┘
organizations <───────────── authsqlite
access <──────────────────── authsqlite
```
@@ -27,6 +29,12 @@ access resolves roles against that hierarchy. Existing `auth` roles remain a
platform-level compatibility surface and do not implicitly grant access to an
organization's data. Emergency access is a separate, expiring, audited grant.
`authwebauthn` owns relying-party policy and ceremony orchestration but not
application routes. It stores only opaque token digests, bounded verifier
session state, credential public records, and audit metadata through an
interface implemented by `authsqlite`. The existing `auth` service issues the
ordinary opaque session only after the passkey verifier succeeds.
The package model is developed from explicit threat and data contracts, not by
moving an existing application's internals into a shared directory. See
[ADOPTION.md](ADOPTION.md), [GETTING_STARTED.md](GETTING_STARTED.md), and the
+33 -1
View File
@@ -2,12 +2,44 @@
# Dependency boundary
Most packages use only the Go standard library. Two direct modules are pinned:
Most packages use only the Go standard library. The principal implementation
dependencies are pinned:
- `golang.org/x/crypto` supplies the reviewed Argon2id implementation used by
`auth` (BSD-3-Clause upstream licence).
- `modernc.org/sqlite` supplies the no-CGO SQLite adapter in `authsqlite`
(BSD-3-Clause upstream licence).
- `github.com/go-webauthn/webauthn` `v0.17.1` supplies the audited source for
WebAuthn Level 3 parsing and cryptographic verification in `authwebauthn`
(BSD-3-Clause upstream licence; source commit
`de0a809e3027957ca15b72b252540317f9ba581b`). Its imported transitive modules
are pinned directly in `go.mod` because the verifier is compiled internally.
The exact, unchanged `go-webauthn` module source is retained at
`third_party/go-webauthn`. The non-test Go files from the packages used by
`authwebauthn` are copied into `internal/webauthnvendored`; only their
self-import prefix is mechanically rewritten. A derivation gate recreates that
internal tree from the audited source and requires a byte-for-byte match before
tests or builds. The complete upstream file manifest, upstream module checksum,
source commit, licence, and downloaded module ZIP SHA-256 are checked in.
The derivative is exercised by ordinary and race-enabled tests, but is excluded
from repository formatting so that gate cannot rewrite the audited upstream
source. The repository still runs `go vet` over the complete graph and permits
only the exact upstream warning for its unexported COSE structure sentinel;
every other vet diagnostic fails verification.
This arrangement is deliberate. A `replace` directive in a library module is
ignored by downstream consumers, so it cannot guarantee which verifier source
an application compiles. The public module has no local replacement and no
direct `github.com/go-webauthn/webauthn` module requirement; applications
compile the checked internal derivative instead. Its transitive modules remain
pinned by `go.mod`, `go.sum`, and SumDB. Release builders populate an isolated
verified module cache before offline compilation.
A conventional repository-wide `go mod vendor` would also copy the SQLite and
full transitive graph, currently roughly 143 MiB and more than 2,300 files.
That unrelated expansion is deliberately avoided: only the security-sensitive
WebAuthn verifier named by the policy is source-vendored here.
Applications that do not import `auth` or `authsqlite` do not link those
implementations into their binaries. Optional GeoIP enrichment is an interface
+55
View File
@@ -0,0 +1,55 @@
<!-- SPDX-License-Identifier: MPL-2.0 -->
# Passkey integration
`authwebauthn` is a passkey ceremony service, not a login page or account
policy. An application supplies its exact relying-party identity, routes,
authorization decisions, session cookie, HTML, and local recovery command.
## Fixed security policy
- Use an exact HTTPS origin whose hostname equals the relying-party ID.
- Reject cross-origin ceremonies.
- Require discoverable credentials and user verification.
- Request no attestation conveyance.
- Permit ES256 only until another algorithm has explicit interoperability and
security evidence.
- Store random challenges and verifier session data only behind opaque,
single-use ceremony tokens.
- Treat clone warnings as audit signals rather than automatic lockout for
synchronized passkeys.
The service uses a random WebAuthn challenge for every ceremony. A sensitive
application operation is bound separately by storing the SHA-256 digest of its
canonical payload with that ceremony. Never substitute an operation hash,
timestamp, UUID, or counter for the random challenge.
## Application flow
1. A local command calls `Bootstrap` or `Recover` and writes the returned
enrollment token once to a newly created mode-`0600` file.
2. A server-rendered enrollment page calls `BeginEnrollment`; the browser uses
`navigator.credentials.create` with the returned `public_key` value.
3. The browser posts the credential and opaque ceremony token to a bounded JSON
endpoint; `FinishRegistration` verifies and stores the public credential.
4. Login uses `BeginLogin`, `navigator.credentials.get`, and `FinishLogin`.
The successful result contains an ordinary opaque `auth` session token.
5. Sensitive operations call `BeginApproval` with a canonical application
payload and `FinishApproval` with those exact same bytes. Any drift fails.
`authhttp.WritePasskeyBegin` and `authhttp.ReadPasskeyFinish` provide bounded
JSON framing only. They do not register routes, authorize requests, serve
JavaScript, or set sessions automatically.
## Recovery and credential lifecycle
Recovery is deliberately host-local and should never be reachable through an
HTTP handler. It revokes all user sessions and pending ceremonies, replaces
prior enrollment tokens, appends a secret-free audit event, and returns one
15-minute token. It does not delete existing passkeys. After enrolling a
replacement, the operator reviews credential labels and removes lost keys with
a fresh passkey-bound removal ceremony. The final passkey cannot be removed
remotely.
Before enabling production mutations, applications should require at least two
independent passkeys and complete a local recovery drill.
+17
View File
@@ -14,6 +14,16 @@ errors, separate safe/sensitive analytics projections, organization-scoped
bindings, single-use invitation digests, and short-lived audited break-glass
grants.
Passkey ceremonies require one exact HTTPS origin and relying-party ID,
discoverable credentials, user verification, and single-use random challenges.
The SQLite adapter consumes enrollment tokens and ceremonies atomically.
Operation approvals retain a separate digest of canonical application state;
the random WebAuthn challenge is never replaced by a predictable state hash.
Cross-origin ceremonies, unsupported credential algorithms, expired state, and
binding drift fail closed. Signature-counter clone warnings are surfaced for
audit but do not automatically lock out multi-device passkeys whose counters
legitimately remain zero.
An application may create an account with a cryptographically generated
temporary credential and `RequirePasswordChange`. Successful rotation compares
the current credential, replaces its Argon2id hash, clears the requirement, and
@@ -32,6 +42,13 @@ generate the credential cryptographically, and write it only to a newly created
private file. A recovery must not reveal whether an account exists through a
public request surface.
Passkey-only recovery never creates a password or remote fallback. A local
administrator revokes sessions and active ceremonies and issues a short-lived
single-use enrollment token. Existing passkeys remain visible so the operator
can review and remove lost credentials after enrolling a replacement. The
library prevents remote removal of the final credential; applications should
require a freshly bound passkey assertion before every removal.
Unsafe methods without an exact Origin or trustworthy same-origin Fetch
Metadata fail the origin check. Authentication middleware fails closed when its
service or `__Host-` cookie policy is invalid. Imported request records have