Raxx · internal docs

internal · gated ↑ index

ADR 0001 — WebAuthn / Passkeys as the Only Authentication Factor

Status: Accepted Date: 2026-04-21 Deciders: product owner (user), software-architect Scope: All user-facing authentication in TradeMasterAPI (Raptor + Antlers)

Context

TradeMasterAPI is adding multi-user identity. The product owner has set a hard invariant: the system must never store replayable credentials, and no password, SMS OTP, or email OTP may serve as the authentication factor. Additionally the platform handles money movement (live trading) — phishing resistance is not a "nice to have", it is a pre-condition for shipping live-mode to users.

We need a single decision: what is the authentication factor?

Decision

WebAuthn / FIDO2 passkeys are the sole authentication factor. Both platform authenticators (Face ID, Touch ID, Windows Hello, Android biometric) and roaming authenticators (YubiKey, Titan, SoloKey, etc.) are accepted. The backend stores only public keys and credential metadata, never a secret that could impersonate the user.

The implementation will rely on standard libraries (probably py-webauthn on the backend and @simplewebauthn/browser on the frontend) — specific version pinned during the implementation sub-card, not here.

Account recovery, when the primary passkey is lost, is an email-gated re-attestation flow, not a fallback factor. See docs/architecture/auth.md §5.3.

Consequences

Positive

Negative / risks

Neutral

Alternatives considered

Passwords (hashed with Argon2id)

Rejected. Violates the invariant — even Argon2id hashes are still credentials we store and could (in theory) allow offline cracking after a breach. Password reset flows re-introduce email-OTP as an implicit factor. No.

Rejected. Turns the email inbox into the single factor for every login, not just recovery. Phishable. Slow. Breaks with aggressive email-link-rewriters (Microsoft Safe Links, Barracuda). Does not meet AAL2.

TOTP / SMS OTP as primary

Rejected. SMS is explicitly out by invariant #3 (email is the only contact channel). TOTP on its own is not an identity — it needs a paired password, which we forbid. TOTP as a second factor on top of passkeys adds zero value because the passkey already requires user verification.

OIDC-only (log in with Google / Apple)

Rejected as the sole strategy. We do not want to make Google Identity a hard dependency for our users, and some traders prefer to avoid it. Passkeys give us provider-independent phishing resistance. We may add "Sign in with Apple / Google" as an additional path in a later ADR, but only if it can be wired through without storing any provider tokens long-term.

SAML-only

Rejected. SAML is an enterprise SSO tool, not a retail-user auth scheme. Overkill and wrong persona.

Client certificates (mTLS) to the browser

Rejected on UX grounds. Managing client certs in a browser is hostile to non-technical users. mTLS remains an option for server-to-server auth in a later ADR.

Compliance checklist

Revisit when