Back to blog
Building a Production-Ready Auth Module in NestJS with Prisma and MySQL

Building a Production-Ready Auth Module in NestJS with Prisma and MySQL

Fawad Iqbal
nestjsprismamysqlauthenticationjwtbackendsecurity

Building a Complete Enterprise-Grade Auth Module in NestJS

Authentication is not just about login and register.
A production system requires secure token management, OTP verification, password reset flows, role-based access control, and strong validation.

This guide walks through building a complete enterprise-ready Auth module using:

  • NestJS
  • Prisma ORM
  • MySQL
  • JWT (Access + Refresh Tokens)
  • OTP verification
  • RBAC (Role-Based Access Control)

1. Architecture Overview

A clean and scalable structure:

modules/auth/
 ├─ controllers/
 ├─ services/
 ├─ dtos/
 ├─ guards/
 ├─ strategies/
 └─ entities/

Responsibilities:

  • Controllers → HTTP layer
  • Services → Business logic
  • DTOs → Validation
  • Guards → Authorization
  • Strategies → JWT validation
  • Prisma → Database layer

2. Prisma User Model

model User {
  id              Int      @id @default(autoincrement())
  username        String
  email           String   @unique
  password        String
  role            String   @default("USER")
  refreshToken    String?
  otpHash         String?
  otpExpiresAt    DateTime?
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
}

Key points:

  • Passwords are hashed
  • Refresh tokens stored hashed
  • OTP stored hashed
  • Role field enables RBAC

3. Core Features

User Registration

  • Validate DTO
  • Hash password (bcrypt)
  • Store user
  • Optional email verification

Login

  • Validate credentials
  • Generate Access Token (15m)
  • Generate Refresh Token (7d)
  • Store hashed refresh token

Refresh Token Rotation

  • Validate stored refresh token
  • Issue new access token
  • Replace refresh token

4. JWT Strategy

Access Token:

  • Short lived (15 minutes)
  • Used for protected routes

Refresh Token:

  • Long lived (7 days)
  • Stored hashed in DB
  • Rotated on refresh

Environment variables:

JWT_SECRET=access_secret
JWT_REFRESH_SECRET=refresh_secret

5. OTP-Based Password Reset

Forgot Password

  • Generate 6-digit OTP
  • Hash OTP before storing
  • Set expiry (10 minutes)
  • Send via email

Reset Password

  • Validate OTP
  • Check expiration
  • Hash new password
  • Clear OTP fields

Security:

  • Limit OTP attempts
  • Never reveal if email exists
  • Log reset attempts

6. Change Password

  • Require JWT authentication
  • Verify old password
  • Hash new password
  • Invalidate existing refresh tokens

7. Update Profile

Protected route.

User can update:

  • Username
  • Email
  • Avatar (optional)

Always validate input using DTOs.


8. Role-Based Access Control (RBAC)

Create RolesGuard:

@Roles('ADMIN')
@Get('admin-only')

Guard checks:

  • JWT validity
  • User role
  • Permission access

9. DTO Validation Example

export class RegisterDto {
  @IsString()
  username: string;

  @IsEmail()
  email: string;

  @IsStrongPassword()
  password: string;
}

Enable global validation:

app.useGlobalPipes(new ValidationPipe({ whitelist: true }));

10. Security Best Practices

  • Hash passwords and tokens
  • Enable HTTPS
  • Use rate limiting
  • Implement refresh token rotation
  • Log auth events
  • Use strong password validation
  • Avoid detailed error messages
  • Keep secrets in environment variables

11. Clean Auth Flow Summary

Register → Hash password → Store user
Login → Validate → Issue tokens
Access Protected Route → JWT Guard
Refresh → Rotate token
Forgot Password → OTP → Reset
RBAC → Role-based Guards


Final Thoughts

An enterprise Auth module must be:

  • Secure
  • Modular
  • Scalable
  • Maintainable

NestJS combined with Prisma provides strong typing, clean architecture, and powerful extensibility for real-world applications.

Authentication is not just a feature --- it is infrastructure.