Implementing Multi-Factor Authentication in Next.js 14
<h2>Why Multi-Factor Authentication?</h2>
<p>Passwords alone are no longer sufficient. With data breaches exposing billions of credentials, MFA has become essential for protecting user accounts. For EU applications, MFA is often required for GDPR compliance when handling sensitive data.</p>
<h2>Types of MFA</h2>
<p>There are three main types of authentication factors:</p>
<ul>
<li><strong>Something you know:</strong> Password, PIN</li>
<li><strong>Something you have:</strong> Phone, authenticator app, hardware key</li>
<li><strong>Something you are:</strong> Fingerprint, face recognition</li>
</ul>
<h2>Implementing TOTP (Authenticator Apps)</h2>
<p>Time-based One-Time Passwords (TOTP) are the most popular MFA method. They work with apps like Google Authenticator, Authy, and 1Password.</p>
<pre><code>// Install required package
npm install otplib qrcode
// Generate secret and QR code import { authenticator } from 'otplib'; import QRCode from 'qrcode';
export async function generateMFASecret(userId: string, email: string) { const secret = authenticator.generateSecret(); const otpauth = authenticator.keyuri(email, 'YourApp', secret); const qrCode = await QRCode.toDataURL(otpauth);
// Store secret in database await User.findByIdAndUpdate(userId, { mfaSecret: secret });
return { secret, qrCode }; }
<h2>Verifying TOTP Codes</h2>
<pre><code>import { authenticator } from 'otplib';
export function verifyMFACode(secret: string, token: string): boolean { return authenticator.verify({ token, secret }); }
// In your login route const isValid = verifyMFACode(user.mfaSecret, req.body.mfaCode); if (!isValid) { return res.status(401).json({ error: 'Invalid MFA code' }); }
<h2>SMS-Based MFA</h2>
<p>SMS is convenient but less secure than TOTP. Use it as a fallback option.</p>
<pre><code>// Using Twilio for SMS
import twilio from 'twilio';
const client = twilio(accountSid, authToken);
export async function sendMFACode(phoneNumber: string) { const code = Math.floor(100000 + Math.random() * 900000).toString();
await client.messages.create({
body: Your verification code is: ${code},
from: '+1234567890',
to: phoneNumber,
});
// Store code in Redis with 5-minute expiration
await redis.setex(mfa:${phoneNumber}, 300, code);
return code; }
<h2>Recovery Codes</h2>
<p>Always provide backup recovery codes in case users lose access to their MFA device.</p>
<pre><code>export function generateRecoveryCodes(count: number = 10): string[] {
return Array.from({ length: count }, () => crypto.randomBytes(4).toString('hex').toUpperCase() ); }
// Store hashed recovery codes const codes = generateRecoveryCodes(); const hashedCodes = codes.map(code => bcrypt.hashSync(code, 10)); await User.findByIdAndUpdate(userId, { recoveryCodes: hashedCodes });
<h2>User Experience Considerations</h2>
<p>MFA should be secure without being frustrating:</p>
<ul>
<li>Make MFA optional for low-risk accounts</li>
<li>Remember devices for 30 days</li>
<li>Provide clear setup instructions</li>
<li>Offer multiple MFA methods</li>
<li>Show recovery codes during setup</li>
</ul>
<h2>Testing MFA</h2>
<p>Test all MFA flows thoroughly:</p>
<ul>
<li>Setup flow (QR code generation, code verification)</li>
<li>Login flow (prompt for MFA, verify code)</li>
<li>Recovery flow (use recovery codes)</li>
<li>Device remembering (skip MFA on trusted devices)</li>
<li>Error handling (invalid codes, expired codes)</li>
</ul>
<h2>Conclusion</h2>
<p>MFA significantly improves account security. By implementing TOTP with proper backup options and a smooth UX, you can protect your users while maintaining a good experience. For EU applications, MFA is becoming a requirement rather than a nice-to-have.</p>
About the Author
Marcus Weber
Lead QA Engineer
Built testing frameworks used by 50+ companies, 12+ years in QAQA Engineer and testing advocate. Built testing frameworks for Fortune 500 companies. Passionate about making testing accessible and effective for all developers.
Get notified of updates
Subscribe to receive an email when this article is updated with new information.
We'll only email you about updates to this specific article. Unsubscribe anytime.Related Articles
View all AuthImplementing SAML SSO for Enterprise B2B Applications
Complete guide to implementing SAML-based Single Sign-On for enterprise customers. Covers SAML flows, metadata exchange, and multi-tenant configuration.
Zero Trust Architecture for Modern Web Applications
Implement zero trust security principles in your web application. Learn continuous verification, least privilege access, and micro-segmentation.
API Key Management and Rotation Strategies
Learn how to securely manage, rotate, and protect API keys in production. Includes key generation, storage, and automated rotation strategies.
Secure Session Management in Next.js with Redis
Best practices for managing user sessions securely in Next.js using Redis. Covers session storage, rotation, and security hardening.
Role-Based Access Control (RBAC) in React Applications
Implement robust role-based access control in React with TypeScript. Includes route protection, component-level permissions, and API authorization.
Social Login Integration: Google, GitHub, and Microsoft
Complete guide to integrating social login providers with GDPR-compliant consent flows. Includes OAuth setup for Google, GitHub, and Microsoft.