Multi-Factor Authentication (MFA) — Complete Implementation Guide
In this tutorial, you will learn about Multi. We cover key concepts, practical examples, and best practices to help you master this topic.
Multi-Factor Authentication (MFA) requires users to provide two or more verification factors to gain access, combining something you know (password), something you have (phone, hardware key), or something you are (biometric) for defense-in-depth security.
What You'll Learn
By the end of this lesson, you will implement TOTP-based MFA with authenticator apps, configure backup codes for account recovery, implement step-up authentication for sensitive actions, and understand WebAuthn/FIDO2 hardware keys.
Why It Matters
Password-only authentication is insufficient for modern security. MFA prevents 99.9% of account compromise attacks. Durga Antivirus Pro requires MFA for all administrator accounts. Doda Browser offers optional MFA for user accounts and enforces it for enterprise SSO.
Real-World Use
A user logs into their email account. After entering their password, the server sends a push notification to their phone. They approve it. Later, they try to change their recovery email. The server requires MFA again for this sensitive action.
MFA Enrollment Flow
sequenceDiagram
participant User
participant Server
participant Authenticator
User->>Server: Enable MFA in settings
Server->>Server: Generate secret key
Server-->>User: Show QR code (otpauth:// URL)
User->>Authenticator: Scan QR code
Authenticator->>Authenticator: Store secret, generate TOTP
User->>Server: Enter current TOTP code
Server->>Server: Verify TOTP matches secret
Server-->>User: MFA enabled
Server-->>User: Show backup codes (single use)
TOTP Implementation
import pyotp
import qrcode
from io import BytesIO
import base64
class TOTPManager:
def __init__(self, issuer="DodaTech"):
self.issuer = issuer
def generate_secret(self):
secret = pyotp.random_base32()
print(f"[TOTP] Secret generated: {secret}")
return secret
def get_provisioning_uri(self, secret, email):
totp = pyotp.TOTP(secret)
uri = totp.provisioning_uri(name=email, issuer_name=self.issuer)
print(f"[TOTP] Provisioning URI generated")
return uri
def verify_code(self, secret, code):
totp = pyotp.TOTP(secret)
is_valid = totp.verify(code)
print(f"[TOTP] Code verification: {'PASSED' if is_valid else 'FAILED'}")
return is_valid
def generate_backup_codes(self, count=8):
import secrets
codes = []
for _ in range(count):
code = secrets.token_hex(5).upper()
codes.append(code)
print(f"[TOTP] Generated {len(codes)} backup codes")
return codes
manager = TOTPManager()
secret = manager.generate_secret()
uri = manager.get_provisioning_uri(secret, "alice@example.com")
print(f"Scan QR in authenticator app:\n{uri}")
test_code = pyotp.TOTP(secret).now()
print(f"Current TOTP: {test_code}")
manager.verify_code(secret, test_code)
Expected output:
[TOTP] Secret generated: JBSWY3DPEHPK3PXP
[TOTP] Provisioning URI generated
Scan QR in authenticator app:
otpauth://totp/DodaTech:alice@example.com?secret=...
[TOTP] Code verification: PASSED
MFA Middleware with Step-Up Auth
const express = require("express");
const speakeasy = require("speakeasy");
const app = express();
app.use(express.json());
const users = {
1: {
email: "alice@example.com",
password: "hashed-password",
mfaSecret: speakeasy.generateSecret({ length: 20 }).base32,
mfaEnabled: true,
role: "user",
},
};
function verifyTOTP(secret, token) {
return speakeasy.totp.verify({
secret,
encoding: "base32",
token,
window: 1,
});
}
app.post("/api/auth/login", (req, res) => {
const { email, password, mfaCode } = req.body;
const user = Object.values(users).find(u => u.email === email);
if (!user || user.password !== password) {
return res.status(401).json({ error: "Invalid credentials" });
}
if (user.mfaEnabled) {
if (!mfaCode) {
return res.json({ requireMfa: true, message: "MFA code required" });
}
if (!verifyTOTP(user.mfaSecret, mfaCode)) {
return res.status(401).json({ error: "Invalid MFA code" });
}
}
console.log(`[MFA] User ${user.email} fully authenticated`);
res.json({ session: "session-token", user: { email: user.email } });
});
app.post("/api/auth/enable-mfa", (req, res) => {
const secret = speakeasy.generateSecret({ length: 20 });
console.log(`[MFA] New secret generated for enrollment`);
res.json({
secret: secret.base32,
qrCode: `otpauth://totp/DodaTech:${req.body.email}?secret=${secret.base32}&issuer=DodaTech`,
});
});
app.post("/api/auth/verify-mfa-setup", (req, res) => {
const { secret, token } = req.body;
if (verifyTOTP(secret, token)) {
const backupCodes = Array.from({ length: 8 }, () =>
require("crypto").randomBytes(4).toString("hex").toUpperCase()
);
console.log(`[MFA] Setup verified, backup codes generated`);
res.json({ success: true, backupCodes });
} else {
res.status(400).json({ error: "Invalid code, try again" });
}
});
app.listen(3000);
Common Mistakes
- Not providing backup codes locks users out if they lose their authenticator device.
- Implementing TOTP without time drift tolerance causes valid codes to be rejected.
- Requiring MFA on every request instead of using step-up authentication for sensitive actions only.
- Storing TOTP secrets in the same database as passwords without encryption.
- Not rate-limiting MFA verification attempts allows brute force of short TOTP codes.
- Failing to handle clock synchronization issues between server and authenticator app.
Practice Questions
- How does TOTP generate time-based codes?
TOTP combines a shared secret with the current Unix timestamp divided into 30-second intervals. The HMAC-SHA1 hash of the secret and time counter produces a 6-8 digit code. Both server and authenticator compute the same code from the same secret and time.
- What is the purpose of backup codes?
Backup codes are single-use codes generated during MFA enrollment. Users store them safely (e.g., print or password manager). If the authenticator device is lost, backup codes provide a recovery method without needing to contact support.
- What is step-up authentication?
Step-up auth requires additional verification only for sensitive actions. A user with MFA already authenticated can access their profile, but changing the password or initiating a wire transfer triggers an additional MFA challenge.
- Challenge: Build a complete MFA system with TOTP authenticator app support, backup codes, step-up authentication for sensitive endpoints, Rate Limiting on MFA verification (3 attempts per 5 minutes), and device trust (remember this device for 30 days).
FAQ
Mini Project: MFA Code Validator
Build a CLI tool that can generate TOTP codes from a secret, validate codes, generate QR code URIs, and manage backup codes.
import pyotp
import sys
import json
from pathlib import Path
class MFATool:
def __init__(self, storage_path=".mfa_secrets.json"):
self.storage_path = Path(storage_path)
self.secrets = self._load()
def _load(self):
if self.storage_path.exists():
return json.loads(self.storage_path.read_text())
return {}
def _save(self):
self.storage_path.write_text(json.dumps(self.secrets, indent=2))
def generate(self, email):
secret = pyotp.random_base32()
self.secrets[email] = secret
self._save()
totp = pyotp.TOTP(secret)
uri = totp.provisioning_uri(email, issuer="DodaTech")
print(f"Secret: {secret}")
print(f"Current code: {totp.now()}")
print(f"Provisioning URI: {uri}")
def code(self, email):
secret = self.secrets.get(email)
if not secret:
print(f"No secret for {email}")
return
totp = pyotp.TOTP(secret)
print(f"Current code: {totp.now()}")
print(f"Remaining: {30 - (int(__import__('time').time()) % 30)}s")
def verify(self, email, code):
secret = self.secrets.get(email)
if not secret:
print(f"No secret for {email}")
return
totp = pyotp.TOTP(secret)
if totp.verify(code, valid_window=1):
print("Code VALID")
else:
print("Code INVALID")
if __name__ == "__main__":
tool = MFATool()
if len(sys.argv) < 2:
print("Commands: generate <email>, code <email>, verify <email> <code>")
elif sys.argv[1] == "generate":
tool.generate(sys.argv[2])
elif sys.argv[1] == "code":
tool.code(sys.argv[2])
elif sys.argv[1] == "verify":
tool.verify(sys.argv[2], sys.argv[3])
What's Next
Learn about passwordless authentication for modern login experiences, then explore social login for third-party identity integration.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro