Introduction

Authentication is a critical aspect of any web application. In this article, we will build a production-ready authentication system using NestJS, Prisma, JWT, and Nodemailer. We'll cover user registration, email verification, password reset, authentication logging, and security best practices.

By the end, you’ll have a solid NestJS authentication boilerplate that can be used in real-world applications. The complete source code is available on GitHub:

🔗 nestjs-auth GitHub Repository


🎯 Why NestJS for Authentication?

NestJS is a progressive Node.js framework that makes backend development structured, scalable, and maintainable. Here’s why NestJS is a great choice for authentication:

Modular architecture – Organizes authentication, user management, and security as separate modules.

TypeScript support – Provides strong typing and cleaner code.

Built-in decorators – Simplifies authentication, validation, and guards.

Scalability – Ideal for building large applications with microservices support.


🛠 Tech Stack & Features

📌 Stack

  • NestJS – Backend framework
  • Prisma – ORM for MySQL
  • JWT (JSON Web Tokens) – Authentication
  • Nodemailer – Email service
  • Helmet.js – Security headers
  • Rate Limiting – Prevent brute force attacks

🔥 Key Features

User Authentication (JWT) – Register, login, logout

Email Verification (via OTP)

Password Reset (via OTP)

Role-Based Access Control (RBAC)

Request Throttling for Security

Logging Authentication Events (IP, status, user info, etc.)

Swagger API Documentation


📦 Project Setup

1️⃣ Clone the Repository

git clone https://github.com/jaleeldgk/nestjs-auth.git
cd nestjs-auth

2️⃣ Install Dependencies

npm install

3️⃣ Set Up Environment Variables

Rename .env.example to .env and update the values:

DATABASE_URL="mysql://user:password@localhost:3306/nest_auth"
JWT_SECRET="your_jwt_secret"
EMAIL_USER="your_smtp_email"
EMAIL_PASS="your_smtp_password"

4️⃣ Set Up Database (MySQL with Prisma)

npx prisma migrate dev --name init
npx prisma generate

5️⃣ Run the Server

npm run start:dev

The API will be available at http://localhost:3000


🔐 Authentication Flow

1️⃣ User Registration & Email Verification

When a user registers, an OTP is sent via email. The user must verify their email before logging in.

Prisma User Model (prisma/schema.prisma)

model User {
  id              String  @id @default(uuid())
  email           String  @unique
  password        String
  name            String
  isVerified      Boolean @default(false)
  emailVerifiedAt DateTime? @default(null)
  createdAt       DateTime @default(now())
}

Register & Send OTP (auth.service.ts)

async register(dto: AuthDto) {
  const hashedPassword = await bcrypt.hash(dto.password, 10);
  const user = await this.prisma.user.create({
    data: { email: dto.email, name: dto.name, password: hashedPassword },
  });

  // Generate OTP
  const otp = Math.floor(100000 + Math.random() * 900000).toString();
  await this.prisma.otp.create({
    data: { userId: user.id, otp, expiresAt: new Date(Date.now() + 10 * 60 * 1000) },
  });

  await this.emailService.sendOTP(user.email, otp);
  return { message: 'OTP sent to email' };
}

2️⃣ User Login & JWT Authentication

After successful email verification, the user can log in and receive a JWT token.

Login API (auth.service.ts)

async signin(dto: AuthDto) {
  const user = await this.prisma.user.findUnique({ where: { email: dto.email } });
  if (!user || !(await bcrypt.compare(dto.password, user.password))) {
    throw new UnauthorizedException('Invalid credentials');
  }

  if (!user.isVerified) {
    throw new ForbiddenException('Email not verified');
  }

  const token = this.jwtService.sign({ id: user.id, role: user.role });
  return { accessToken: token };
}

3️⃣ Password Reset (OTP Based)

  • User requests a password reset.
  • A one-time password (OTP) is sent via email.
  • The user enters the OTP and sets a new password.

Generate & Send OTP (auth.service.ts)

async requestPasswordReset(email: string) {
  const user = await this.prisma.user.findUnique({ where: { email } });
  if (!user) throw new NotFoundException('User not found');

  const otp = Math.floor(100000 + Math.random() * 900000).toString();
  await this.prisma.otp.create({
    data: { userId: user.id, otp, expiresAt: new Date(Date.now() + 10 * 60 * 1000) },
  });

  await this.emailService.sendOTP(user.email, otp);
  return { message: 'OTP sent to email' };
}

🛡 Security Enhancements

🔹 Rate Limiting (Throttle)

To prevent brute force attacks, we add rate limiting using @nestjs/throttler.

import { Throttle } from '@nestjs/throttler';

@Throttle(5, 60) // Allow 5 requests per minute per IP
async signin(@Body() dto: AuthDto) {
  return this.authService.signin(dto);
}

🔹 Helmet for Security Headers

import helmet from 'helmet';
app.use(helmet()); // Adds security headers

📖 API Documentation (Swagger)

We use Swagger for API documentation.

Access it at:

📌 http://localhost:3000/api/docs

Enable Swagger in main.ts

const config = new DocumentBuilder()
  .setTitle('NestJS Auth API')
  .setDescription('Authentication system with JWT, OTP, and Email Verification')
  .setVersion('1.0')
  .build();

const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document);

🚀 Future Upgrades

  • Winston Logging for Authentication Events
  • Improved RBAC with Permissions
  • Docker Support for Deployment
  • Multi-Tenant Authentication
  • Two-Factor Authentication (2FA)

💡 Conclusion

In this guide, we built a secure authentication system with NestJS featuring JWT authentication, email verification, password reset, and security best practices. The project is modular, scalable, and production-ready.

🚀 Ready to use it? Check out the source code:

🔗 GitHub Repository


💬 What’s Next?

Are there any features you'd like to see? Let me know in the comments! 😊