DEV Community

Jaleel Ahmad
Jaleel Ahmad

Posted on

Building a Production-Ready Authentication System with NestJS, Prisma, and JWT

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
Enter fullscreen mode Exit fullscreen mode

2️⃣ Install Dependencies

npm install
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

4️⃣ Set Up Database (MySQL with Prisma)

npx prisma migrate dev --name init
npx prisma generate
Enter fullscreen mode Exit fullscreen mode

5️⃣ Run the Server

npm run start:dev
Enter fullscreen mode Exit fullscreen mode

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())
}
Enter fullscreen mode Exit fullscreen mode

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' };
}
Enter fullscreen mode Exit fullscreen mode

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 };
}
Enter fullscreen mode Exit fullscreen mode

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' };
}
Enter fullscreen mode Exit fullscreen mode

πŸ›‘ 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);
}
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή Helmet for Security Headers

import helmet from 'helmet';
app.use(helmet()); // Adds security headers
Enter fullscreen mode Exit fullscreen mode

πŸ“– 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);
Enter fullscreen mode Exit fullscreen mode

πŸš€ 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! 😊

Top comments (0)