AiTechWorlds
AiTechWorlds
TOTP (Google Authenticator), SMS OTP, FIDO2/WebAuthn/Passkeys, recovery codes, and MFA enrollment best practices.

Multi Factor Authentication (MFA) requires users to verify identity using two or more factors from different categories:
2FA (Two Factor Authentication) is MFA with exactly two factors. Any MFA implementation significantly reduces account takeover risk, even if the password is compromised.
| Method | Security | Phishing Resistant | UX | Cost |
|---|---|---|---|---|
| FIDO2 / Passkey | Highest | Yes | Excellent | Hardware optional |
| Hardware key (YubiKey) | Highest | Yes | Good | $25โ80/key |
| TOTP app (Authenticator) | High | No | Good | Free |
| Push notification | High | Partial | Excellent | Varies |
| SMS OTP | Medium | No | Good | Per SMS |
| Email OTP | Low Medium | No | Good | Free |
| Security question | Low | No | Poor | Free |
TOTP is the most widely deployed MFA method, used by Google Authenticator, Authy, Microsoft Authenticator.
1. Setup: Server generates a random secret (20 bytes) and shows it as QR code
2. User scans QR with authenticator app
3. App and server share the secret
At login:
4. Server generates code: HMAC-SHA1(secret, floor(now/30))
5. User opens app โ app generates same code independently
6. User enters code โ server verifies match
7. Code expires after 30 secondsimport pyotp
import qrcode
# Generate secret (store securely per user)
secret = pyotp.random_base32()
# JBSWY3DPEHPK3PXP (store this for the user)
# Generate TOTP URI for QR code
totp = pyotp.TOTP(secret)
uri = totp.provisioning_uri(
name="alice@example.com",
issuer_name="MyApp"
)
# otpauth://totp/MyApp:alice@example.com?secret=...&issuer=MyApp
# Generate QR code
qr = qrcode.make(uri)
qr.save("qr.png")
# Verify user input
def verify_totp(user_secret: str, user_input: str) -> bool:
totp = pyotp.TOTP(user_secret)
return totp.verify(user_input, valid_window=1) # ยฑ1 window for clock driftโ Store the secret encrypted at rest
โ One-time use per code โ log and reject previously used codes (replay attack prevention)
โ valid_window=1 (accept ยฑ30s) accommodates clock drift
โ Never accept multiple windows (valid_window โฅ 3) โ weakens securityCounter based OTP code advances with each use rather than by time.
import pyotp
hotp = pyotp.HOTP(secret)
print(hotp.at(0)) # Code at counter 0
print(hotp.at(1)) # Code at counter 1
# Verify (with counter sync window)
hotp.verify("123456", counter=5, look_ahead_window=3)While better than no MFA, SMS OTP is vulnerable to:
import random
import string
from datetime import datetime, timedelta
def generate_otp(length=6) -> str:
return ''.join(random.choices(string.digits, k=length))
def send_otp(phone: str, otp: str):
# Store OTP hash + expiry in database
expiry = datetime.utcnow() + timedelta(minutes=10)
db.store_otp(phone, hash_otp(otp), expiry)
sms_provider.send(phone, f"Your verification code: {otp}. Valid for 10 minutes.")
def verify_otp(phone: str, user_input: str) -> bool:
stored = db.get_otp(phone)
if not stored or stored.expiry < datetime.utcnow():
return False
if stored.attempts >= 3: # rate limit
raise TooManyAttempts()
db.increment_attempts(phone)
return hmac.compare_digest(hash_otp(user_input), stored.hash)The strongest and most phishing resistant authentication method. The private key never leaves the device.
Registration:
1. Server sends challenge (random bytes)
2. Browser calls navigator.credentials.create()
3. Authenticator creates key pair, signs the challenge
4. Public key + credential ID sent to server โ stored
5. Private key stays on device (never transmitted)
Authentication:
1. Server sends challenge
2. Browser calls navigator.credentials.get()
3. Authenticator signs challenge with private key
4. Server verifies signature with stored public key
5. Login granted โ no password ever transmitted// WebAuthn Registration
const credential = await navigator.credentials.create({
publicKey: {
challenge: serverChallenge, // random bytes from server
rp: { name: "MyApp", id: "myapp.com" },
user: { id: userId, name: email, displayName: name },
pubKeyCredParams: [
{ type: "public-key", alg: -7 }, // ES256
{ type: "public-key", alg: -257 }, // RS256
],
authenticatorSelection: {
userVerification: "required", // biometric or PIN
},
timeout: 60000,
}
});
// Send credential.response to server for verification and storage
// WebAuthn Authentication
const assertion = await navigator.credentials.get({
publicKey: {
challenge: serverChallenge,
rpId: "myapp.com",
allowCredentials: [{ id: credentialId, type: "public-key" }],
userVerification: "required",
}
});
// Send assertion to server for verificationโ Offer multiple MFA methods (TOTP + SMS as backup)
โ Provide recovery codes at enrollment (8โ12 single-use codes)
โ Store recovery codes as hashed values, not plaintext
โ Send enrollment confirmation email
โ Allow trusted devices (skip MFA for 30 days on same device)
โ Show when MFA was last used
โ Force re-enrollment if secret compromised
โ Rate limit MFA attempts (lock after 5 failures)import secrets
def generate_recovery_codes(count=10, length=10) -> list[str]:
codes = []
for _ in range(count):
# Format: XXXXX-XXXXX
raw = secrets.token_hex(length // 2)
formatted = f"{raw[:5]}-{raw[5:]}"
codes.append(formatted)
return codes
# Show to user ONCE, store hashed
hashed_codes = [bcrypt.hashpw(c.encode(), bcrypt.gensalt()) for c in codes]Get more notes like this daily on Telegram!
Free study notes, cheat sheets & AI tips
Last reviewed on June 13, 2026 by the AiTechWorlds Notes Team. Free cheat sheet no signup required.
Join AiTechWorlds on Telegram and get daily AI tips, prompt engineering templates, coding resources, and exclusive content 100% free!
No spam. Leave anytime.