How to Implement Two-Factor Authentication (2FA) in Next.js
Complete guide to implementing TOTP-based two-factor authentication in your Next.js application with otplib, QR codes, and backup codes.
December 9, 2024
3 min read
By FastSaaS Team
How to Implement Two-Factor Authentication (2FA) in Next.js
Two-factor authentication (2FA) is essential for securing user accounts. This guide shows you how to implement TOTP-based 2FA in your Next.js application.
What is TOTP?
Time-based One-Time Password (TOTP) is an algorithm that generates a unique 6-digit code every 30 seconds using a shared secret key. Popular apps like Google Authenticator and Authy use this standard.
Prerequisites
Install the required packages:
npm install otplib qrcode @types/qrcode
Step 1: Generate Secret and QR Code
// lib/auth/two-factor.ts
import { authenticator } from "otplib";
import QRCode from "qrcode";
export async function generateTwoFactorSecret(email: string) {
const secret = authenticator.generateSecret();
const appName = "FastSaaS";
const otpAuthUrl = authenticator.keyuri(email, appName, secret);
const qrCodeDataUrl = await QRCode.toDataURL(otpAuthUrl);
return {
secret,
qrCodeDataUrl,
otpAuthUrl,
};
}
Step 2: Verify TOTP Codes
export function verifyTOTP(secret: string, token: string): boolean {
return authenticator.verify({
token,
secret,
});
}
Step 3: Generate Backup Codes
import crypto from "crypto";
export function generateBackupCodes(count: number = 8): string[] {
const codes: string[] = [];
for (let i = 0; i < count; i++) {
const code = crypto.randomBytes(4).toString("hex").toUpperCase();
codes.push(`${code.slice(0, 4)}-${code.slice(4, 8)}`);
}
return codes;
}
Step 4: Database Schema (Prisma)
model User {
id String @id @default(cuid())
email String @unique
twoFactorEnabled Boolean @default(false)
twoFactorSecret String?
twoFactorBackupCodes String[]
}
Step 5: Enable 2FA API Route
// app/api/auth/two-factor/enable/route.ts
import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { generateTwoFactorSecret } from "@/lib/auth/two-factor";
import { prisma } from "@/lib/prisma";
export async function POST() {
const session = await getServerSession();
if (!session?.user?.email) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { secret, qrCodeDataUrl } = await generateTwoFactorSecret(
session.user.email
);
// Store the secret temporarily (not enabled yet)
await prisma.user.update({
where: { email: session.user.email },
data: { twoFactorSecret: secret },
});
return NextResponse.json({ qrCodeDataUrl });
}
Step 6: Verify and Activate 2FA
// app/api/auth/two-factor/verify/route.ts
import { NextResponse } from "next/server";
import { verifyTOTP, generateBackupCodes } from "@/lib/auth/two-factor";
export async function POST(request: Request) {
const { code } = await request.json();
const session = await getServerSession();
const user = await prisma.user.findUnique({
where: { email: session?.user?.email! },
});
if (!user?.twoFactorSecret) {
return NextResponse.json(
{ error: "No 2FA setup in progress" },
{ status: 400 }
);
}
if (!verifyTOTP(user.twoFactorSecret, code)) {
return NextResponse.json({ error: "Invalid code" }, { status: 400 });
}
const backupCodes = generateBackupCodes();
await prisma.user.update({
where: { id: user.id },
data: {
twoFactorEnabled: true,
twoFactorBackupCodes: backupCodes,
},
});
return NextResponse.json({ success: true, backupCodes });
}
Step 7: Login Flow with 2FA
// Modify your login callback
if (user.twoFactorEnabled) {
// Don't complete login yet, require 2FA code
return {
requiresTwoFactor: true,
userId: user.id,
};
}
Security Best Practices
- Encrypt secrets at rest - Use encrypted database fields
- Hash backup codes - Store hashed versions, invalidate after use
- Rate limit verification - Prevent brute force attacks
- Provide recovery options - Email-based recovery as fallback
Conclusion
2FA significantly improves your application's security. FastSaaS includes a complete 2FA implementation out of the box, saving you development time.
Get FastSaaS with built-in 2FA support!