Implementing Multi-Factor Authentication (MFA)
1. Understanding MFA Factors
| Factor | Type | Example |
|---|---|---|
| Knowledge | Something you know | Password, PIN, security question |
| Possession | Something you have | Phone, hardware key, smart card |
| Inherence | Something you are | Fingerprint, face, voice |
| Location | Somewhere you are | IP geo, GPS, network |
| Behavior | Something you do | Typing pattern, gait |
Note: True MFA requires factors from different categories (e.g., password + phone, not password + security question).
2. Implementing SMS-Based OTP
| Aspect | Detail |
|---|---|
| Code length | 6 digits |
| Validity | 5–10 min |
| Rate limit | 1 send / 60 sec, 5 / hour |
| Verification attempts | 5 max per code |
| Provider | Twilio, AWS SNS, Vonage |
| Risk | SIM swap — NIST deprecated SMS for high-assurance |
3. Implementing Email-Based OTP
| Aspect | Detail |
|---|---|
| Code length | 6–8 digits or token link |
| Validity | 10–15 min |
| Risk | Only as strong as user's email account |
| Deliverability | SPF/DKIM/DMARC configured |
4. Using Time-Based OTP
Example: TOTP (RFC 6238) generation
import { authenticator } from "otplib";
const secret = authenticator.generateSecret(); // base32, 160 bits
const uri = authenticator.keyuri(user.email, "Acme", secret);
// Show QR code of uri to user
// Verify
const ok = authenticator.verify({ token: userInput, secret });
| Parameter | Default |
|---|---|
| Algorithm | HMAC-SHA1 |
| Digits | 6 |
| Period | 30 sec |
| Window | ±1 step (clock skew) |
5. Implementing Authenticator Apps
| App | Notes |
|---|---|
| Google Authenticator | Cloud-sync since 2023 |
| Microsoft Authenticator | Push notifications + TOTP |
| Authy | Multi-device, encrypted backup |
| 1Password / Bitwarden | Built into password managers |
| Provisioning URI | otpauth://totp/Issuer:user?secret=...&issuer=... |
6. Using Hardware Tokens
| Token | Standard | Phish-Resistant? |
|---|---|---|
| YubiKey | FIDO2 / WebAuthn / U2F / OTP | Yes (FIDO2) |
| Titan Key | FIDO2 / U2F | Yes |
| RSA SecurID | OTP | No |
| Smart Card (PIV) | X.509 | Yes |
7. Implementing Biometric Authentication
| Platform | API |
|---|---|
| iOS | LocalAuthentication / Face ID, Touch ID |
| Android | BiometricPrompt |
| Web | WebAuthn (platform authenticator) |
| Windows | Windows Hello |
8. Implementing Push Notifications
Push MFA Flow
- User submits password → backend issues challenge ID
- Push notification sent to registered device
- User taps Approve / Deny in app (biometric optional)
- App signs challenge with device key → posts to server
- Server verifies signature → completes auth
- Display number-matching code to prevent fatigue attacks
Warning: Plain "approve" push prompts are vulnerable to MFA fatigue. Use number matching (user enters/confirms 2-digit code shown on login screen).
9. Setting Up Backup Codes
| Aspect | Detail |
|---|---|
| Count | 8–10 codes |
| Format | 10-char alphanumeric (e.g., x7k9-p4m2-q1n8) |
| Storage | Hashed (bcrypt/argon2) — not retrievable |
| Use | Single-use, mark consumed on success |
| Regenerate | Invalidate all on regen |
10. Enforcing MFA Policies
| Policy | Trigger |
|---|---|
| Required | All users (highest assurance) |
| Risk-based | New device, suspicious IP |
| Role-based | Admins always, others optional |
| Resource-based | Step-up for sensitive endpoints |
| Grace period | 14 days to enroll after signup |
11. Implementing Step-Up Authentication
Example: ACR-based step-up (OIDC)
// Sensitive endpoint requires stronger AuthN
app.delete("/account", (req, res, next) => {
if (req.user.acr !== "urn:mfa:phishing-resistant") {
return res.status(401).json({
error: "insufficient_authentication",
acr_values: "urn:mfa:phishing-resistant",
max_age: 300 // require recent auth
});
}
next();
});
12. Handling MFA Enrollment Flow
TOTP Enrollment
- User initiates "Enable 2FA"
- Server generates secret + QR code (provisional, NOT yet saved as enrolled)
- User scans QR in authenticator app
- User enters current TOTP code to confirm
- Server verifies code → marks MFA enabled, generates backup codes
- Display backup codes ONCE → user must save/print
- Send confirmation email