Two-Factor Authentication (2FA / MFA): Ship It Without Locking Customers Out
2FA Strategy for Your New SaaS
Goal: Ship two-factor authentication that customers actually adopt — TOTP and passkeys as the primary methods, SMS as the last-resort fallback, recovery codes generated and shown once, account-recovery flows that don't require human intervention 90% of the time. Avoid the failure modes where founders ship SMS-only 2FA (vulnerable to SIM swaps, expensive, can lock out international users), skip recovery codes (one lost phone = a support ticket and three days of customer pain), or leave 2FA optional forever (a single phished admin = your worst day).
Process: Follow this chat pattern with your AI coding tool such as Claude or v0.app. Pay attention to the notes in [brackets] and replace the bracketed text with your own content.
Timeframe: TOTP + recovery codes shipped in 2-3 days. Passkeys + 2FA-required-for-admins in week 1. SMS fallback (if needed) and customer-facing docs in week 2. Quarterly review baked in.
Why Most Founder 2FA Is Broken
Three failure modes hit founders the same way:
- SMS-only 2FA. Founder picks Twilio, ships SMS verification, calls it done. Six months later: a customer's SIM gets swapped; their account is taken over. Twilio's Verify pricing creeps up as international SMS rates rise. International users on prepaid plans complain that codes don't arrive. The whole feature becomes a liability.
- No recovery codes. Customer enables TOTP on their phone, then loses the phone. They contact support. You have no automated recovery; a support agent has to verify their identity manually, which means email-based "are you really you" exchanges that aren't actually secure. The customer rage-tweets; you spend hours on a single recovery.
- Optional forever. 2FA is shipped but never enforced. Power users enable it; everyone else doesn't. The first phishing attack hits a customer's admin account; 200 of their users are exposed. You announce mandatory 2FA the next week — too late.
The version that works is structured: TOTP and passkeys as primary methods, recovery codes generated at enrollment, SMS only as a knowing-the-tradeoff opt-in, mandatory 2FA for admins by default, and an automated recovery flow that doesn't require humans for the common cases.
This guide assumes you have already done Authentication (2FA layers on top), have considered Auth Providers (Clerk, Supabase, Better Auth ship 2FA primitives), have shipped SSO & Enterprise Auth (enterprise customers expect 2FA enforcement settings), and have shipped Audit Logs (every 2FA event should be logged).
1. Pick the Methods You'll Support
Not all 2FA is created equal. Pick the methods first; the rest of the work follows.
Help me decide which 2FA methods to support in [my product].
The methods, ranked by security and UX in 2026:
**1. Passkeys (WebAuthn) — strongest, best UX**
- Synced via iCloud / Google / Microsoft accounts
- Phishing-resistant by design
- No codes, no apps, just biometric on the user's device
- Works on iOS 16+, Android 9+, recent macOS / Windows
- Should be your DEFAULT 2FA method in 2026
- Single passkey can replace password + 2FA entirely
**2. TOTP (Time-based One-Time Password) — universal, well-understood**
- Apps: Google Authenticator, 1Password, Bitwarden, Authy, Apple Passwords, Yubico Authenticator
- 6-digit code rotates every 30 seconds
- Works offline; no per-use cost
- Universally supported; users know how it works
- Strong fallback when passkeys aren't usable
**3. WebAuthn / hardware security key (FIDO2) — enterprise-grade**
- YubiKey, Google Titan, etc.
- Phishing-resistant
- Required by some enterprise procurement (NIST AAL2/AAL3)
- Niche for indie SaaS; high user friction
- Often grouped with passkeys in code
**4. Recovery codes — not 2FA, but mandatory companion**
- 10 single-use codes generated at enrollment
- Stored by the user (offline, in 1Password, written down)
- Account recovery without human intervention
- Always generate; always show once
**5. SMS — fallback only, NOT primary**
- Vulnerable to SIM swap attacks
- Expensive at scale (Twilio Verify $0.05+ per check)
- Doesn''t work for some international users
- NIST officially deprecated SMS as primary 2FA in 2017
- Use only when nothing else works for the user
**6. Email codes — same problems as SMS, plus weak password layer**
- Acceptable for "step up" auth (re-verify before sensitive action) but NOT for primary 2FA
- Email account compromise = bypassing your 2FA
**7. Push notifications via your mobile app**
- Only works if you have a mobile app
- Convenient when it works
- Not a fit for most indie SaaS without a mobile presence
**The 2026 default stack for indie SaaS**:
- Primary: Passkeys (WebAuthn)
- Fallback: TOTP
- Recovery: 10 single-use recovery codes
- Optional: SMS (only with explicit user consent and warning)
- Skip: email codes for primary 2FA
Output:
1. The methods I''m shipping in v1 (recommend: passkeys + TOTP + recovery codes)
2. The methods I''m explicitly NOT shipping (and why)
3. The plan for SMS, if any (typical: ship later if customers ask)
4. Whether I need WebAuthn / hardware-key support for enterprise
The biggest unforced error: shipping SMS-only and calling it done. SMS is the worst commonly-used 2FA method on every dimension that matters: security, cost, reliability. Ship TOTP first. Add SMS later only if a real customer demand justifies it.
2. Default to Passkeys
Passkeys are the headline 2FA improvement of 2025–2026. Lower friction than TOTP, phishing-resistant, and increasingly universal.
Help me ship passkeys as primary 2FA.
The pattern:
**Storage**:
```sql
CREATE TABLE user_passkeys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
credential_id BYTEA NOT NULL UNIQUE,
public_key BYTEA NOT NULL,
counter BIGINT NOT NULL DEFAULT 0,
device_type TEXT, -- "iCloud Keychain", "Google", "YubiKey", etc.
device_name TEXT, -- user-set label, e.g., "MacBook Pro"
transports TEXT[], -- ['internal', 'hybrid', 'usb', 'nfc', 'ble']
aaguid UUID, -- authenticator model GUID
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
last_used_at TIMESTAMP,
revoked_at TIMESTAMP -- soft-delete
);
CREATE INDEX idx_user_passkeys_user ON user_passkeys(user_id) WHERE revoked_at IS NULL;
Enrollment flow:
- User clicks "Add passkey" in security settings
- Backend generates a challenge via WebAuthn library (e.g.,
@simplewebauthn/server,passage,passkey-kit) - Browser prompts user with platform UI (Touch ID, Face ID, Windows Hello, etc.)
- Browser returns the credential to your backend
- Backend verifies the attestation; stores
credential_id,public_key,device_type,device_name - Show success message: "Passkey added: [Device Name]"
Login flow:
- User enters email (or username, or just clicks "Sign in with passkey")
- Backend generates a challenge
- Browser prompts with available passkeys
- User authenticates via biometric or PIN
- Backend verifies; logs the user in
- Increment
counter; updatelast_used_at
Critical implementation rules:
- Use a library.
@simplewebauthn/server(Node),webauthn(Python),webauthn4j(Java), or your auth provider's built-in support. Don''t implement WebAuthn from scratch. - Treat passkeys as both first and second factor. A passkey-protected login can replace password + TOTP. Most users prefer this.
- Allow multiple passkeys per user. Phone, laptop, desktop — each gets a passkey. Show them all in security settings; allow individual revocation.
- Show the device name and last-used timestamp so the user can spot a passkey they don''t recognize.
- Counter validation: WebAuthn includes a signature counter; if a returned counter is less than or equal to the stored counter, the credential may be cloned — reject and alert.
Edge cases to handle:
- User has no passkey-capable device → fall back to TOTP
- User cleared browser data and is signing in from a synced-but-not-yet-enrolled device → guide them through enrolling on the new device
- User's syncing service (iCloud, Google Password Manager) is broken → recovery codes are the safety net
Don''t:
- Require passkeys without offering a fallback (TOTP and recovery codes)
- Mix up WebAuthn challenges across requests (each challenge must be single-use)
- Skip the attestation verification
Output:
- The user_passkeys schema migration
- The enrollment endpoint code (challenge + verification)
- The login endpoint code (challenge + verification)
- The security-settings UI for managing passkeys
- The fallback path when passkeys aren''t available
The single biggest UX win in 2026 2FA: **a one-click "Sign in with passkey" button that bypasses email and password entirely.** No code copy-paste, no app switching. It''s why iOS / Android / Microsoft are pushing passkeys hard. Ship this and watch your sign-in times drop.
---
## 3. Ship TOTP as Universal Fallback
TOTP is the universal 2FA method that works for users without passkey support. Mature, well-understood, no per-use cost.
Help me ship TOTP.
The pattern:
Storage:
CREATE TABLE user_totp (
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
secret_encrypted BYTEA NOT NULL, -- AES-encrypted; key from secret manager
enabled_at TIMESTAMP NOT NULL DEFAULT NOW(),
last_used_at TIMESTAMP,
last_used_code TEXT -- to prevent reuse within the 30s window
);
Enrollment flow:
- User clicks "Set up TOTP" in security settings
- Backend generates a 160-bit secret via
crypto.randomBytes(20) - Backend stores the secret encrypted (using KMS / your secret manager)
- Backend renders a QR code with
otpauth://totp/[Issuer]:[user_email]?secret=…&issuer=[Issuer] - User scans the QR code with their authenticator app
- User enters a code from the app to confirm enrollment
- Backend verifies; if valid, marks TOTP as enabled
- Backend generates 10 recovery codes (per next section); shows them to the user once
Login flow:
- User enters email + password (or completes passkey login)
- If TOTP is enabled, prompt for code
- User enters 6-digit code from authenticator app
- Backend verifies code via TOTP library (e.g.,
otpauth,pyotp,google-authenticator) - Backend verifies the code is not the same as
last_used_code(prevents replay within window) - Update
last_used_codeandlast_used_at; complete login
Critical implementation rules:
- Encrypt secrets at rest. A TOTP secret is as sensitive as a password. Use KMS / your secret manager to encrypt the secret column.
- Use a battle-tested library. Don''t implement HOTP / TOTP from scratch.
- Allow a small clock skew window (typically ±1 step = 30 seconds before/after) to handle slight clock drift on the user''s device.
- Reject reused codes. A 6-digit code is valid for ~30 seconds; a code can be entered twice within that window. Reject the second use.
- Include the issuer in the otpauth URI. Authenticator apps display this; it should be your product name so users can identify the entry.
- Show the secret as text below the QR code so users on devices that can't scan can manually enter it.
Don''t:
- Store the TOTP secret in plain text
- Generate a new secret on every page load (the secret is generated once, at enrollment)
- Send the secret over an insecure channel (always HTTPS)
- Skip rate-limiting the verification endpoint (5 attempts per 15 minutes is reasonable)
Output:
- The user_totp schema migration
- The enrollment flow with QR code rendering
- The login flow with code verification
- The library choice (e.g.,
otpauthfor Node) - The encryption strategy for the secret column
- The rate-limiter on the verification endpoint
The single most-skipped detail: **encrypting the TOTP secret at rest.** A breach that exposes plaintext TOTP secrets means every TOTP user is owned. Encrypt with a KMS key the database can''t decrypt on its own.
---
## 4. Generate and Show Recovery Codes Once
Recovery codes are the safety net. Without them, a lost device becomes a support ticket. With them, recovery is self-serve.
Help me ship recovery codes.
The pattern:
Storage:
CREATE TABLE user_recovery_codes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
code_hash TEXT NOT NULL, -- bcrypt or sha256 hash of the code
used_at TIMESTAMP -- NULL if not yet used
);
CREATE INDEX idx_recovery_codes_user ON user_recovery_codes(user_id) WHERE used_at IS NULL;
Generation:
// Generate 10 codes when 2FA is first enabled (and on regen request)
function generateRecoveryCodes(userId: string) {
const codes = []
for (let i = 0; i < 10; i++) {
const raw = crypto.randomBytes(5).toString('hex') // 10-char hex
const formatted = raw.slice(0, 5) + '-' + raw.slice(5) // "a3f8b-c2d4e"
codes.push(formatted)
}
// Hash and store
for (const code of codes) {
db.insert('user_recovery_codes', { user_id: userId, code_hash: sha256(code) })
}
return codes // return raw codes ONCE for display
}
Display flow:
- User completes TOTP / passkey enrollment
- Backend generates 10 codes; stores hashes; returns raw codes once in the response
- UI shows codes prominently with a "Download" button (text file) and a "Print" button
- UI shows a stark warning: "Save these codes now. You won''t see them again. Each code can be used once."
- Confirmation checkbox: "I''ve saved my recovery codes" — required to dismiss the modal
- After dismissal, raw codes are gone; only hashes remain
Use flow (when user has lost their device):
- User clicks "I lost my 2FA device" on the login page
- Prompt for recovery code
- Backend hashes the input; looks up
user_recovery_codes WHERE user_id = ? AND code_hash = ? AND used_at IS NULL - If found: mark
used_at = NOW(); complete login; redirect to security settings - Force the user to set up new 2FA before they can do anything else
- Show a notification: "You have N recovery codes remaining."
Regen flow:
- User clicks "Regenerate recovery codes" in security settings
- Confirm: "This will invalidate your existing codes."
- Generate 10 new codes; delete old ones; show new codes once
- Email the user: "Your recovery codes were regenerated on [date] from [IP]"
Critical implementation rules:
- Hash the codes at rest. Use bcrypt or sha256 — recovery codes have enough entropy that sha256 is acceptable.
- Always 10 codes. Single-use; ten gives users buffer.
- Format for human readability.
a3f8b-c2d4eis much harder to typo thana3f8bc2d4e. - Force re-enrollment after recovery code use. A code-based login means the user lost their device; they need to set up a new device immediately.
- Notify on regeneration. If a regeneration was unintentional, the user finds out via email.
Don''t:
- Store recovery codes in plain text
- Allow recovery codes to bypass other security checks (still require password)
- Let recovery codes be reused (single-use, full stop)
- Email the codes (codes go through email = email account compromise = full account compromise)
Output:
- The user_recovery_codes schema
- The generation function
- The show-once UI with mandatory confirmation
- The recovery login flow
- The regeneration flow with notification
The single biggest support-load reduction: **recovery codes shown at enrollment.** A customer who lost their phone and has their recovery codes recovers in 30 seconds. A customer who didn''t save their codes is a 2-hour support ticket and a frustrated NPS detractor.
---
## 5. Make 2FA Required for High-Privilege Roles
Optional 2FA is fine for free-tier individual accounts. Workspace Admins, billing managers, and enterprise tiers should require it.
Design the 2FA enforcement policy.
The patterns:
Pattern 1: Required by default for workspace admins
- When a user is promoted to Admin role (per Roles & Permissions), they must enable 2FA before they can perform admin actions
- Show a banner: "Your account requires 2FA. Set it up to continue."
- Block destructive actions (delete workspace, manage billing, invite users) until enabled
- This is the strongest single security improvement most products can make
Pattern 2: Workspace-level enforcement (admin-controlled)
- Workspace admin can require 2FA for all members of their workspace
- New members are prompted to enable on first login
- Existing members get a deadline (typically 14 days) before being blocked
- Useful for enterprise tiers as a sales-led feature
Pattern 3: Tier-based enforcement
- Free tier: 2FA optional
- Paid tier: 2FA recommended (banner)
- Enterprise tier: 2FA required (config-toggleable)
- Aligns enforcement with security expectations of each tier
Critical UX details:
- Grace period before blocking. Don''t lock out an admin mid-action. Show banners; remind via email; warn before blocking.
- Show why. "2FA is required because you''re an admin / your workspace requires it / your tier requires it." Explanation reduces confusion.
- Make enrollment fast. Setup-to-done in under 2 minutes. Long flows kill compliance.
- Allow temporary bypass for legitimate edge cases. A senior admin needs to reset their own 2FA after a phone loss; document the support process clearly.
Server-side enforcement:
- The auth middleware checks
if (user.requires_2fa && !user.has_2fa_enabled) return 403 - Specific high-privilege endpoints check this; not every endpoint
- Per Audit Logs, log every enforcement event
Don''t:
- Force 2FA on all users in v1 (creates onboarding friction; users churn)
- Block users without warning (creates support load)
- Skip the policy for enterprise customers who specifically request it
Output:
- The enforcement-policy table with role/tier mappings
- The middleware that enforces 2FA for protected actions
- The grace-period UI flow
- The admin-controlled workspace setting
- The audit-log entries for enforcement events
The single biggest security improvement most SaaS products can make: **requiring 2FA for workspace admins.** The blast radius of an admin phish is enormous. Lock that door first; broaden later.
---
## 6. Build the Account-Recovery Flow
When recovery codes are lost too, you need a fallback that doesn't require human intervention 90% of the time.
Design the recovery flow.
The tiers:
Tier 1: Recovery code (90%+ of cases)
- Per step 4: user enters one of their saved codes; logs in; re-enrolls
- Self-serve; no support involvement
Tier 2: Email-based recovery (with cooldown)
- User clicks "I lost everything" on login
- Email them a magic link to a recovery flow
- The link starts a 72-hour security waiting period
- During the waiting period: send daily emails ("Your account is in recovery; cancel here if this wasn't you")
- After 72 hours: allow the user to disable 2FA and log in
- The 72-hour delay is the security mechanism — if the email account itself is compromised, the user has time to notice
Tier 3: Identity verification (paying customers; rare)
- For high-value accounts where Tier 2 is too slow
- Verify identity via: government ID upload + selfie, or video call with support
- Manual; expensive; only for paying customers
- Logged extensively for audit
Tier 4: Workspace admin override (multi-user accounts)
- Another admin in the same workspace can disable a user's 2FA
- Useful when the user's account has admin colleagues
- Logged; the disabled user is notified by email
Critical rules:
- The recovery flow is itself authenticated. Don''t create a recovery URL anyone can hit.
- Always email the user about recovery actions. Notification is the audit trail and the alarm.
- Cooldowns exist for a reason. Don''t skip the 72-hour wait; that''s how you protect against email compromise.
- Document the flow in customer-facing docs. The fewer surprises during recovery, the better.
- Audit every recovery event (per Audit Logs).
Don''t:
- Allow support agents to disable 2FA without identity verification + audit
- Skip the email notification (silent recovery = silent compromise)
- Make the recovery flow harder than the password reset flow — they''re analogous
Output:
- The four-tier recovery flow
- The 72-hour cooldown implementation
- The customer-facing recovery docs
- The internal admin runbook for Tier 3 cases
- The audit-log entries for every tier
The single most-overlooked design: **the cooldown.** A 5-second recovery flow defeats 2FA entirely (compromise the email, recover the account, done). A 72-hour cooldown gives the legitimate user time to notice. Friction is the feature.
---
## 7. Audit Every 2FA Event
Without audit, "did someone disable my 2FA?" becomes guesswork.
Design the audit log integration for 2FA events.
Per Audit Logs, log:
Enrollment events:
auth.2fa.enabled— method (totp / passkey / sms), device labelauth.2fa.disabled— by whom, whyauth.passkey.added— device nameauth.passkey.revoked— by whom
Login events:
auth.2fa.success— method used (sample at 10% to avoid log spam)auth.2fa.failure— method, reason (wrong code, expired, replay)
Recovery events:
auth.recovery_code.used— how many remainingauth.recovery_codes.regeneratedauth.recovery.tier2_started— email-based; logs IP and user-agentauth.recovery.tier3_started— manual; logs support agent IDauth.2fa.admin_disabled— when an admin removes another user''s 2FA
Anomaly events:
auth.2fa.replay_attempt— same TOTP code submitted twiceauth.passkey.counter_anomaly— counter went backward (possible cloning)auth.2fa.brute_force_blocked— rate limiter triggered
Customer-facing security activity at /account/security:
- Last 50 auth events
- Filterable by type, IP, date range
- Lets the customer investigate "did I really log in from London at 3am?"
Notifications (in addition to audit):
- Email the user when 2FA is enabled, disabled, or methods change
- Email the user when a new passkey is added
- Email the user when recovery codes are regenerated
- Email the user when 2FA fails repeatedly (potential brute-force)
Don''t:
- Log raw codes or secrets in audit metadata
- Skip notifications (the email is the user''s anomaly detector)
- Log every success at full volume (sample to keep storage costs sane)
Output:
- The audit event schema for 2FA
- The notification email templates
- The customer-facing security activity feed
- The retention policy
The single most useful audit signal: **"new passkey added on [device] from [IP]."** A user who didn't add a passkey reading that email knows their account is compromised. Without the email, they wouldn't know until something worse happened.
---
## 8. Document for Customers
Customers won't enable 2FA they don't trust. Clear docs are the difference between 30% and 90% adoption.
Help me draft the customer-facing 2FA documentation.
Sections:
Why we offer 2FA
- One paragraph: 2FA stops ~99% of account takeover attempts
- What changes for them after enabling (one extra step at sign-in)
Methods we support
- Passkeys (recommended)
- TOTP / authenticator apps (also great)
- Recovery codes (mandatory companion to TOTP)
- SMS (only as a last resort; document the tradeoffs)
Setting up
- Step-by-step UI walkthrough for each method
- Screenshots / short videos
- The "save your recovery codes" reminder, repeated
Using 2FA at sign-in
- What to expect
- What to do if your code doesn''t work
- How to use a recovery code
If you lose your device
- Tier 1: use a recovery code
- Tier 2: email-based recovery (72-hour wait)
- Tier 3: identity verification (paying customers only)
- Clear walkthroughs of each
Managing devices
- How to add a new passkey
- How to revoke a passkey from a lost device
- How to switch authenticator apps
For workspace admins
- How to require 2FA for your workspace
- What happens to existing members
- How to help a teammate who lost their device
FAQ
- Why don''t you support email codes? (explain why they''re weak)
- Why is SMS not recommended? (SIM swap, cost, reliability)
- What if my authenticator app crashes? (recovery codes)
- Can I use a hardware security key? (YubiKey support)
Output:
- The docs page structure
- The step-by-step walkthroughs with screenshots
- The FAQ
- The video script for "setting up 2FA in 90 seconds"
The biggest predictor of 2FA adoption: **how clearly the docs explain recovery.** Users don''t enable 2FA they don''t trust. "If you lose your device, here are three ways to recover" reassures users that 2FA isn''t a one-way trip.
---
## 9. Monitor Adoption
Without metrics, you don't know if 2FA is doing anything for your customer base.
Set up 2FA adoption monitoring.
Metrics to track:
- % of accounts with 2FA enabled (overall and by tier)
- % of admins with 2FA enabled (target: 100% if enforcing)
- % of new users who enable 2FA in their first 30 days (proxy for onboarding pressure)
- TOTP vs passkey vs SMS distribution (track passkey share over time — should grow)
- Recovery code usage rate (high rate = customers losing devices = product opportunity to remind users to add a second passkey)
- 2FA failure rate (target: <2% of attempts; higher = UX issue or attack)
Alerts:
- A single account with 5+ failed 2FA attempts in 5 minutes (possible attack)
- Admin disables 2FA on a user account (audit + notify)
- 2FA enrollment rate drops month-over-month (regression in the flow)
- Passkey counter anomaly (possible cloning)
Customer-facing signals:
- Show the user what % of their workspace has 2FA enabled (gentle nudge)
- "Add a backup passkey" prompt for users with only one passkey
- Reminder banner for users without 2FA after a security event in the news
Output:
- The metrics emission code
- The alert rules
- The customer-facing adoption widget
- The monthly adoption report template
The biggest adoption-rate lever: **default to 2FA-required for admins.** If you ship optional 2FA to free users and required 2FA to admins, adoption skews toward 100% on the high-leverage segment without alienating the rest.
---
## 10. Quarterly Review
2FA infrastructure rots. Quarterly review keeps it healthy.
The quarterly review checklist.
Health metrics:
- What % of accounts have 2FA enabled? Target trends up.
- What % of admins have 2FA? Should be ~100%.
- What % of 2FA users use passkeys vs TOTP? Passkey share should grow.
- Recovery code usage rate — any anomalies?
- Failure rate — any spikes?
Method support:
- Are passkey libraries up to date? (WebAuthn spec evolves)
- Is the TOTP library patched?
- Are SMS provider rates current? (If using SMS)
Documentation review:
- Are screenshots and walkthroughs current?
- Has the recovery flow changed since the docs were written?
Security posture:
- Has any auth provider in our stack had a CVE this quarter? Patch.
- Are we logging the events we said we''d log?
- Have any users reported account-takeover incidents? Run post-mortem.
Customer feedback:
- Top support tickets related to 2FA — what pattern?
- Sales objections related to 2FA / SSO posture?
- Any enterprise prospect ask for stricter enforcement?
Output:
- Health snapshot
- 3 fixes for next quarter
- 1 method to consider adding (e.g., hardware keys for enterprise)
- 1 deprecation (e.g., phasing out SMS)
---
## What "Done" Looks Like
A working 2FA system in 2026 has:
- **Passkeys (WebAuthn) as primary**, TOTP as universal fallback
- **Recovery codes generated and shown once** at enrollment
- **No SMS as primary** (SMS only as opt-in fallback with explicit warning)
- **Required for workspace admins** by default
- **Workspace-level enforcement** for enterprise tiers
- **Multi-tier account recovery** with 72-hour cooldown for email-based recovery
- **Audit logs** for every enrollment, login, and recovery event
- **Email notifications** on every security-relevant change
- **Adoption monitoring** with alerts on anomalies
- **Customer-facing docs** with clear walkthroughs for each method and recovery tier
- **Quarterly review** baked into the team rhythm
The 2FA system you build in week 1 will look broken by year 2 if you don''t review it. Customers'' security teams ask "what 2FA methods do you support? do you require it for admins? what''s your recovery flow?" during enterprise sales — being able to answer cleanly closes deals; pointing at SMS-only loses them. Build it right; enforce it for the right people; review it quarterly.
---
## See Also
- [SSO & Enterprise Auth](sso-enterprise-auth-chat.md) — enterprise customers expect 2FA enforcement
- [Roles & Permissions (RBAC)](roles-permissions-chat.md) — admin role triggers 2FA enforcement
- [API Keys & PATs](api-keys-chat.md) — keys are the machine-auth equivalent
- [Audit Logs](audit-logs-chat.md) — every 2FA event logged
- [Multi-Tenant Data Isolation](multi-tenancy-chat.md) — workspace-level enforcement settings
- [Authentication](https://www.vibereference.com/auth-and-payments/authentication) — 2FA layers on top
- [Auth Providers](https://www.vibereference.com/auth-and-payments/auth-providers) — Clerk, Supabase, Better Auth ship 2FA primitives
- [Passkeys](https://www.vibereference.com/auth-and-payments/passkeys) — deep-dive on the WebAuthn flow
- [Secret Management Providers](https://www.vibereference.com/devops-and-tools/secret-management-providers) — encrypt TOTP secrets at rest
- [Notification Providers](https://www.vibereference.com/backend-and-data/notification-providers) — security emails go through here
[⬅️ Growth Overview](README.md)