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! 😊