Xây dựng hệ thống xác thực OTP qua Email với Next.js - Phần 1: Thiết lập và cấu hình
Bài viết này (Phần 1) hướng dẫn cài đặt và cấu hình nền tảng cho hệ thống xác thực OTP qua email bằng Next.js. Các bước chính bao gồm cài đặt nodemailer, tạo "Mật khẩu ứng dụng" (App Password) của Gmail để gửi email, và thiết lập các biến môi trường. Bài viết cũng định nghĩa hai Mongoose model quan trọng: OTPVerification (để lưu mã OTP, theo dõi số lần thử sai, và tự động hết hạn) và cập nhật User model (thêm trường emailVerified).

Giới thiệu
Trong bài viết này, chúng ta sẽ xây dựng một hệ thống xác thực email hoàn chỉnh sử dụng mã OTP (One-Time Password) cho ứng dụng Next.js. Hệ thống này bao gồm:
✅ Gửi mã OTP 6 chữ số qua email
✅ Email template đẹp mắt với HTML/CSS
✅ Xác thực OTP với giới hạn số lần thử
✅ Rate limiting để tránh spam
✅ UI/UX thân thiện với auto-focus và paste support
✅ Tích hợp với NextAuth.js
✅ Responsive design cho mobile
Tech Stack
Next.js 15.x - React framework
MongoDB + Mongoose - Database
NextAuth.js v5 - Authentication
Nodemailer - Email service
Gmail SMTP - Email provider (miễn phí)
TypeScript - Type safety
Tailwind CSS - Styling
Phần 1: Cài đặt và cấu hình
Bước 1: Cài đặt dependencies
400">npm install nodemailer
400">npm install --save-dev @types/nodemailer
Bước 2: Cấu hình Gmail SMTP
2.1. Tạo Gmail App Password
Google không cho phép sử dụng mật khẩu thông thường để gửi email từ ứng dụng bên ngoài. Bạn cần tạo App Password:
Đăng nhập vào tài khoản Google của bạn
Truy cập: https://myaccount.google.com/security
Bật 2-Step Verification (Xác minh 2 bước)
Sau khi bật 2FA, tìm mục App passwords (Mật khẩu ứng dụng)
Chọn app: Mail và device: Other (Custom name)
Nhập tên: "My Next.js App"
Nhấn Generate
Lưu lại mật khẩu 16 ký tự (dạng:
xxxx xxxx xxxx xxxx)
2.2. Cấu hình biến môi trường
Tạo/cập nhật file .env.local:
# Gmail SMTP Configuration
GMAIL_USER=your-email@gmail.com
GMAIL_APP_PASSWORD=xxxx xxxx xxxx xxxx
# Site Configuration
NEXT_PUBLIC_SITE_NAME=Your App Name
# Database (nếu chưa có)
MONGODB_URI=your_mongodb_connection_string
⚠️ Quan trọng: Thêm .env.local vào .gitignore để không commit lên Git!
Bước 3: Tạo TypeScript types cho Nodemailer
Nếu gặp lỗi TypeScript với nodemailer, tạo file src/types/nodemailer.d.ts:
declare module 'nodemailer' {
export interface TransportOptions {
host: string;
port: number;
secure: boolean;
auth: {
user: string | undefined;
pass: string | undefined;
};
}
export interface MailOptions {
from: {
name: string;
address: string;
};
to: string;
subject: string;
html: string;
text: string;
}
export interface SendMailResult {
messageId: string;
}
export interface Transporter {
sendMail(mailOptions: MailOptions): Promise<SendMailResult>;
verify(): Promise<boolean>;
}
export function createTransport(options: TransportOptions): Transporter;
}
Bước 4: Tạo OTP Verification Model
Tạo file src/models/OTPVerification.ts:
import mongoose, { Schema, Document, Model } from "mongoose";
export interface IOTPVerification extends Document {
email: string;
otp: string;
createdAt: Date;
expiresAt: Date;
verified: boolean;
attempts: number;
}
const OTPVerificationSchema = new Schema<IOTPVerification>({
email: {
type: String,
required: true,
lowercase: true,
trim: true,
},
otp: {
type: String,
required: true,
},
createdAt: {
type: Date,
default: Date.now,
},
expiresAt: {
type: Date,
required: true,
},
verified: {
type: Boolean,
default: false,
},
attempts: {
type: Number,
default: 0,
},
});
// Index for automatic cleanup of expired OTPs
OTPVerificationSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
// Index for faster lookups
OTPVerificationSchema.index({ email: 1, verified: 1 });
const OTPVerification: Model<IOTPVerification> =
mongoose.models.OTPVerification ||
mongoose.model<IOTPVerification>("OTPVerification", OTPVerificationSchema);
export default OTPVerification;
Giải thích:
expiresAt: TTL (Time To Live) index tự động xóa OTP hết hạnattempts: Đếm số lần nhập sai (giới hạn 5 lần)verified: Đánh dấu OTP đã được xác thựcComposite index trên
emailvàverifiedđể tối ưu query
Bước 5: Cập nhật User Model
Nếu User model chưa có trường emailVerified, thêm vào:
// src/models/User.ts
const UserSchema: Schema<IUser> = new Schema(
{
// ... các fields khác
emailVerified: {
type: Boolean,
default: false,
index: true,
},
provider: {
type: String,
enum: ['credentials', 'google'],
default: 'credentials',
},
// ... các fields khác
},
{
timestamps: true,
}
);
Kiến trúc hệ thống
┌─────────────────────────────────────────────────────────┐
│ User Registration │
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ POST /api/auth/register │
│ - Tạo user với emailVerified: false │
│ - Return requireVerification: true │
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ OTPVerification Component Mount │
│ - Auto gửi OTP đến email │
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ POST /api/auth/send-otp │
│ 1. Validate email │
│ 2. Check rate limiting (60s) │
│ 3. Generate 6-digit OTP │
│s 4. Save to OTPVerification model (TTL 10 min) │
│ 5. Send email via Gmail SMTP │
└──────────────────────┬──────────────────────────────────┘
│
E ▼
┌─────────────────────────────────────────────────────────┐
│ User nhập OTP (6 digits) │
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ POST /api/auth/verify-otp │
│ 1. Tìm OTP record │
│ 2. Check expiration │
│ 3. Check attempts (max 5) │
│s 4. Verify OTP │
│ 5. Update user.emailVerified = true │
│ 6. Delete OTP record TA │
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Redirect to Login Page │
│ User có thể đăng nhập │
└─────────────────────────────────────────────────────────┘
Security Features
Rate Limiting: Chỉ cho phép gửi OTP mỗi 60 giây
Attempt Limiting: Tối đa 5 lần nhập sai
TTL: OTP tự động hết hạn sau 10 phút
Auto Cleanup: MongoDB TTL index tự động xóa OTP hết hạn
Auth Middleware: Chặn đăng nhập nếu email chưa verify
Kết thúc Phần 1
Trong phần này, chúng ta đã:
✅ Cài đặt và cấu hình dependencies
✅ Thiết lập Gmail SMTP
✅ Tạo database models
✅ Hiểu rõ kiến trúc hệ thống
Tiếp theo: Phần 2 : Email Service và API
Bài viết này là phần 1 của series "Xây dựng hệ thống xác thực OTP qua Email". Các phần tiếp theo sẽ đi sâu vào implementation chi tiết.