NextJS

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).

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

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

bash
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:

  1. Đăng nhập vào tài khoản Google của bạn

  2. Truy cập: https://myaccount.google.com/security

  3. Bật 2-Step Verification (Xác minh 2 bước)

  4. Sau khi bật 2FA, tìm mục App passwords (Mật khẩu ứng dụng)

  5. Chọn app: Mail và device: Other (Custom name)

  6. Nhập tên: "My Next.js App"

  7. Nhấn Generate

  8. 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:

yaml
# 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:

javascript
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:

typescript
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ạn

  • attempts: Đếm số lần nhập sai (giới hạn 5 lần)

  • verified: Đánh dấu OTP đã được xác thực

  • Composite index trên emailverified để 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:

typescript
// 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

code
┌─────────────────────────────────────────────────────────┐
│                     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

  1. Rate Limiting: Chỉ cho phép gửi OTP mỗi 60 giây

  2. Attempt Limiting: Tối đa 5 lần nhập sai

  3. TTL: OTP tự động hết hạn sau 10 phút

  4. Auto Cleanup: MongoDB TTL index tự động xóa OTP hết hạn

  5. 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.

👁️11 lượt xem
❤️0 lượt thích