111 lines
6.1 KiB
Markdown
111 lines
6.1 KiB
Markdown
<!-- 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.
|
|
- Keep production origins portless. For local development only,
|
|
`AllowDevelopmentPort` permits one explicit non-default port when the RP ID
|
|
is exactly `localhost` or beneath the reserved `.test` top-level domain. The
|
|
configured origin, browser `Origin`, and WebAuthn verifier origin must still
|
|
match exactly.
|
|
- 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.
|
|
- Enforce that policy from the verified COSE public key embedded in
|
|
authenticator data. Do not rely on the optional browser
|
|
`publicKeyAlgorithm` convenience member: direct standards-compliant response
|
|
serializers may omit it even when the attested credential is ES256.
|
|
- 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 `authwebauthn.Bootstrap`, `authwebauthn.Recover`, or
|
|
`bootstrap.Start` and writes the returned enrollment token once to a newly
|
|
created mode-`0600` file. Use `bootstrap.Start` for the first application
|
|
owner so identity, organization membership, direct owner access, and audits
|
|
cannot be partially committed.
|
|
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; authenticated self-service flows use
|
|
`FinishRegistrationForUser` so the application session's user ID is checked
|
|
before any public credential is stored.
|
|
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.
|
|
|
|
For an existing password-backed account, call `BeginPasswordMigration` only
|
|
from an authenticated account session and finish with
|
|
`FinishPasswordMigration`. The registration ceremony is bound to that user.
|
|
Successful completion stores the passkey, removes the password credential,
|
|
clears the password-change flag, revokes every session and pending ceremony,
|
|
and appends the audit event atomically. The application must clear the current
|
|
session cookie and return the user to passkey login after success.
|
|
|
|
`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
|
|
|
|
Administrator-assisted `authwebauthn.Recover` is deliberately host-local and
|
|
must 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.
|
|
|
|
An account may separately expose self-service password-plus-recovery-code
|
|
recovery through `authrecovery`. `Begin` verifies the password, consumes one
|
|
printable code, revokes sessions, and returns a short-lived grant—not a normal
|
|
session. Keep that grant in a narrowly scoped, Secure, HttpOnly, SameSite cookie
|
|
and never place it in a URL. `BeginPasskey` binds its digest into the WebAuthn
|
|
ceremony. `FinishPasskey` atomically consumes the grant, stores the verified
|
|
replacement passkey, replaces the entire recovery-code set, revokes any
|
|
sessions or ceremonies created during recovery, and returns the new plaintext
|
|
codes exactly once. It does not issue a session; return the user to normal
|
|
login after displaying and saving the new codes.
|
|
|
|
A failed storage commit leaves the restricted grant available for a fresh
|
|
ceremony until expiry. A binding mismatch consumes the mismatched ceremony.
|
|
Applications must use generic failure responses and the same credential-attempt
|
|
rate limiting as login.
|
|
|
|
Owner-assisted recovery is a third, deliberately separate path. Configure
|
|
`authrecovery.Options.OwnerRole`, authorize an active direct organization owner,
|
|
and bind that owner's fresh passkey assertion to the exact organization,
|
|
target user, request identifier, and bounded human-review reason before calling
|
|
`IssueAssistedRecovery`. The SQLite transaction rechecks the active direct
|
|
owner and target membership, invalidates the target's password, passkeys,
|
|
recovery codes, sessions, and pending ceremonies, then stores only a digest of
|
|
the 15-minute grant with identity and organization-visible audits.
|
|
|
|
Deliver the returned grant exactly once in a URL fragment. A public recovery
|
|
page can pass it to `BeginAssistedPasskey` and `FinishAssistedRecovery` while
|
|
keeping it out of request URLs, referrers, and access logs. Completion consumes
|
|
the grant atomically with one replacement password, passkey, recovery-code set,
|
|
and both audit trails. It issues no session. Losing the fragment after issuance
|
|
requires another reviewed owner or root-local recovery; old authenticators
|
|
must not become valid again as a fallback.
|
|
|
|
Before enabling production mutations, applications should require at least two
|
|
independent passkeys and complete a local recovery drill.
|