Two-Factor Authentication Explained (and How to Add It to Your App)
What two-factor authentication is, how authenticator-app codes (TOTP) work, why SMS codes are the weakest option, where passkeys fit, and how to add 2FA to your own app — including recovery codes and the mistakes to avoid.
Passwords get leaked, reused and guessed. Two-factor authentication (2FA) — also called multi-factor authentication (MFA) — means a stolen password alone isn't enough to get into an account. Here's how it works, and what it takes to add it to an app you're building.
The idea
Login factors come in three kinds:
- Something you know — a password.
- Something you have — your phone, a security key.
- Something you are — a fingerprint or face.
2FA requires two different kinds. The usual combination: your password, plus a code from your phone. An attacker in another country with your leaked password still can't log in without your phone.
The common 2FA methods, from weakest to strongest
SMS codes
A code is texted to your phone. Better than nothing, but the weakest option: attackers can take over phone numbers with SIM-swap scams (convincing a carrier to move your number to their SIM), and texts can be intercepted. It also costs you money per message.
Email codes
Similar idea, via email. Only as secure as the email account — which is often protected by the same password.
Authenticator apps (TOTP)
Apps like Google Authenticator, Microsoft Authenticator, 1Password or Authy generate a new six-digit code every 30 seconds. This is TOTP — time-based one-time passwords — and it's the most common "proper" 2FA:
- When you turn on 2FA, the app shows a QR code containing a secret key.
- Your authenticator app stores that secret.
- Both your phone and the server combine the secret with the current time to produce the same six digits.
- No network needed — the codes work offline.
Passkeys and security keys
Passkeys (built on the WebAuthn standard) use your device's fingerprint or face unlock, or a hardware security key like a YubiKey. They're phishing-resistant: a fake login page on a lookalike domain can't use them, because the key is tied to the real website. That's something code-based 2FA can't protect against — a convincing fake site can ask for your code and relay it immediately.
Passkeys can also replace passwords entirely, which is where login is heading.
Adding 2FA to your app
The easy way: use your auth provider
If you use an authentication provider or library — Supabase Auth, Clerk, Auth0, Better Auth and others — check its documentation for MFA. Most support TOTP, and many support passkeys. Turning it on is far safer than building it yourself. (How to add login to an AI-built app.)
If you build TOTP yourself
Use a well-known TOTP library rather than writing the algorithm. The flow:
Setting it up:
- Generate a random secret for the user.
- Show it as a QR code (and as text, for manual entry).
- Ask the user to enter a code from their app, and verify it before turning 2FA on. That proves they've saved it correctly.
- Store the secret encrypted in your database — it's as sensitive as a password.
- Generate recovery codes (below).
Logging in:
- Check the password as usual.
- If 2FA is on, ask for a code before creating the full session.
- Verify it, allowing for a small clock difference (most libraries accept the previous and next 30-second window).
- Rate-limit attempts — six digits is only a million combinations. (What is rate limiting?)
- Don't accept the same code twice.
Recovery codes: don't skip them
People lose phones. Without a backup, they're locked out — and your support inbox fills with requests you can't safely verify.
When 2FA is enabled, generate around ten single-use recovery codes, show them once, and tell the user to store them somewhere safe. Store only hashed versions in your database, like passwords. (Password hashing explained.)
Common mistakes
- Checking 2FA in the frontend only. The server must refuse to create a session until the code is verified.
- Letting the 2FA step be skipped by going directly to another URL.
- Disabling 2FA without re-authentication. Require the password and a current code to turn it off.
- Password reset bypassing 2FA. A reset should not remove 2FA; otherwise anyone with email access defeats it. (Password resets done right.)
- No rate limiting on code attempts.
Should your app require 2FA?
- Offer it to everyone once you have real users.
- Require it for admin accounts and anyone with access to other people's data.
- Turn it on yourself for every service your app depends on: your domain registrar, GitHub, your host, your email, and your payment provider. An attacker in any of those is an attacker in your app.
The summary
- 2FA requires two kinds of proof — usually a password plus something on your phone.
- Authenticator apps (TOTP) are the common, solid choice; SMS is weakest; passkeys are strongest and phishing-resistant.
- Use your auth provider's built-in MFA where possible.
- If building it: verify on setup, encrypt secrets, rate-limit, and provide recovery codes.
EasySpawn connects to GitHub via OAuth rather than your personal keys, and runs each server as its own isolated machine — fewer credentials to steal in the first place. See how it works or join the waitlist.
Related: Magic Link Login · "Sign in with Google" Explained · Session vs JWT · OWASP Top 10 Explained
Keep reading
Magic Link Login: How Passwordless Email Sign-In Works (and Its Pitfalls)
Magic links let people log in by clicking a link in their email — no password. How they work, when they're a good fit, the security details that matter (expiry, single use, token hashing), and the real-world problems: spam filters, email scanners and phones vs laptops.
What Is IDOR? The Security Bug Where Users Can See Each Other's Data
IDOR (insecure direct object reference) is when changing an ID in a URL shows you someone else's data. How it happens, why it's the most common serious bug in AI-built apps, how to test for it in five minutes, and the one-line habit that prevents it.