Complete architecture for TOTP, WebAuthn/Passkeys, and backup codes in the eToro Plus ecosystem
| Secret | Storage | Encryption |
|---|---|---|
| TOTP Secrets | PostgreSQL (encrypted blob) | AES-256-GCM via Vault Transit |
| Encryption Master Key | HSM (FIPS 140-2 L3) | Hardware-protected |
| WebAuthn Private Keys | Client device (Secure Enclave) | Device-managed |
| WebAuthn Public Keys | PostgreSQL | Plaintext (public) |
| Backup Codes | PostgreSQL (hashed) | bcrypt (cost 10) |
| Challenge Nonces | Redis (TTL 120s) | N/A (ephemeral) |
| Resource | Limit | Window |
|---|---|---|
| TOTP validation | 5 attempts | 5 minutes |
| WebAuthn auth | 5 attempts | 5 minutes |
| Backup code verify | 3 attempts | 15 minutes |
| TOTP setup | 3 requests | 1 hour |
| Account recovery | 1 request | 24 hours |
Implemented via @nestjs/throttler with Redis backing store. Lockout triggers alert to security team after 3 consecutive lockouts.
Every 2FA event is logged to 2fa_audit_log for compliance (FCA/CySEC). Logs are immutable (append-only) and retained for 7 years.
{
"id": "uuid",
"userId": "12345",
"action": "totp_validate_success",
"ipAddress": "203.0.113.42",
"userAgent": "eToro Plus/3.2.1 (iOS 17.3)",
"deviceFingerprint": "sha256:abc...",
"timestamp": "2026-02-22T14:22:00Z",
"metadata": { "method": "totp", "attemptsInWindow": 1 }
}
crypto.timingSafeEqual() for code comparison| Criteria | TOTP Authenticator | Passkey (WebAuthn) |
|---|---|---|
| Phishing Resistance | No โ User can be tricked into entering code on fake site | Yes โ Origin-bound; cryptographically tied to RP ID |
| User Experience | Open app โ read code โ type 6 digits (10-15s) | Tap button โ biometric (Face ID/fingerprint) โ done (2-3s) |
| Works Offline | Yes | Yes (platform authenticator) |
| Cross-Device | Yes โ Any authenticator app | Yes โ Synced passkeys + hybrid transport (QR) |
| Secret Exposure | Shared secret on server + client | Private key never leaves device/enclave |
| Replay Protection | Time window (~90s vulnerability) | Challenge-response; each auth unique |
| Man-in-the-Middle | Vulnerable | Protected โ TLS channel binding |
| Device Support | Universal (any TOTP app) | iOS 16+, Android 9+, Windows 10+, macOS Ventura+ |
| Passwordless Capable | No | Yes โ Discoverable credentials |
| Cost per Auth | $0 | $0 |
| Implementation Complexity | Low (otplib) | Medium (@simplewebauthn) |
Passkeys are synced WebAuthn credentials โ the private key is securely replicated across a user's devices via platform credential managers. This eliminates the "lost device = locked out" problem.
Key insight: eToro never sees the private key. Only the public key is stored server-side. Sync is handled entirely by the platform (Apple/Google/Microsoft) using end-to-end encryption.
When a user needs to authenticate on a device without a registered passkey (e.g., logging in on a friend's laptop), the hybrid transport protocol enables using their phone as an authenticator via QR code + Bluetooth proximity.
With discoverable credentials (resident keys), the credential includes the user handle โ enabling login without entering a username. The browser's autofill UI suggests matching passkeys for the site.
| Platform | Passkey Support | Sync | Cross-Device (Hybrid) | Conditional UI | Notes |
|---|---|---|---|---|---|
| iOS 16+ / Safari | โ Full | iCloud Keychain | โ | โ | Best passkey support; 97% of iPhones eligible |
| Android 9+ / Chrome | โ Full | Google Password Manager | โ | โ | Requires Google Play Services |
| macOS Ventura+ / Safari | โ Full | iCloud Keychain | โ | โ | Touch ID or Apple Watch |
| macOS / Chrome 118+ | โ Full | iCloud Keychain or Chrome | โ | โ | Chrome profiles synced via Google |
| Windows 10+ / Chrome/Edge | โ Full | Windows Hello (local) | โ | โ | TPM 2.0 required; sync coming via Microsoft |
| Windows / Firefox 122+ | โ | OS-level | โ | Partial | Delegates to Windows Hello |
| Linux / Chrome | Partial | No sync | โ | Partial | USB security keys + phone-as-authenticator |
| Security Keys (YubiKey) | โ | No (single device) | N/A | N/A | FIDO2 USB/NFC; enterprise users |
| Day | Focus | Deliverables | Owner |
|---|---|---|---|
| Day 1 (Mon) | Project Setup + Core TOTP | NestJS scaffold, DB schemas (all 5 tables), TypeORM entities, TOTP service (otplib), setup/verify/validate/disable endpoints working | Backend |
| Day 2 (Tue) | WebAuthn/Passkey + All Endpoints | WebAuthn service (@simplewebauthn/server), register options/verify, authenticate options/verify, list/delete/rename credentials, backup codes generate/verify, status, recovery โ all 19 endpoints done | Backend |
| Day 3 (Wed) | Client TOTP Flows | TOTP setup wizard (QR display, code input, backup codes screen), TOTP login verification screen, CodeInput component, QRCodeDisplay component | Frontend |
| Day 4 (Thu) | Client Passkey Flows | Passkey registration flow, passkey login (biometric), manage passkeys (list/rename/delete), conditional UI (autofill), cross-device QR flow, passkey nudge bottom sheet | Frontend |
| Day 5 (Fri) | Backup & Recovery | Backup code input screen, recovery flow, fallback chain (passkey โ TOTP โ backup โ recovery), disable 2FA flow, all edge case handling | Full-stack |
| Day 6 (Sat) | Testing & QA | E2E tests (TOTP, WebAuthn, backup codes), integration tests, rate limiting verification, security audit (replay protection, timing attacks, brute force), Swagger docs finalized | QA + Security |
| Day 7 (Sun) | Deploy & Go-Live Prep | Staging deployment, load testing, security team sign-off, monitoring/alerting (Datadog), runbook for on-call, feature flag configuration, go-live checklist complete | DevOps + All |
AI-speed timeline: Full 2FA system (TOTP + Passkeys + backup codes) built, tested, and deployed in 1 week. SMS migration nudges ship on Day 7 go-live.