Authentication System with Next.js and Nest.js

Authentication System with Next.js and Nest.js

Publish Date: Feb 16
0 0

Image description
Image description
Image description


Building a Secure Authentication System with Next.js and Nest.js

Security is a top priority in modern web applications, and a well-architected authentication system is key to protecting user data. In this guide, we’ll build a secure authentication system using Next.js and Nest.js, implementing Crypto.js, bcrypt, JWT, and role-based authentication. Additionally, we'll cover how to integrate NodeMailer for password reset functionality with OTP-based sessions.

Why This Architecture?

Our system follows a modular and layered security approach:

Crypto.js (Frontend Encryption) – Encrypts credentials before transmission.

bcrypt (Backend Encryption) – Hashes passwords before storing them in the database.

JWT Authentication – Issues secure access tokens for API requests.

Role-Based Access Control (RBAC) – Restricts users based on roles.

OTP and NodeMailer for Password Reset – Provides secure session-based password recovery.


Step 1: Setting Up the Backend with Nest.js

1.1 Install Dependencies

In your Nest.js backend, install the required packages:

npm install @nestjs/jwt @nestjs/passport bcryptjs passport passport-jwt crypto-js nodemailer
Enter fullscreen mode Exit fullscreen mode

1.2 User Entity & Authentication Service

Define the user schema in TypeORM:

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  email: string;

  @Column()
  password: string;

  @Column({ default: 'user' }) // Default role is 'user'
  role: string;
}
Enter fullscreen mode Exit fullscreen mode

Now, create the authentication service:

import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
  constructor(private jwtService: JwtService) {}

  async hashPassword(password: string): Promise<string> {
    return bcrypt.hash(password, 10);
  }

  async comparePasswords(password: string, hash: string): Promise<boolean> {
    return bcrypt.compare(password, hash);
  }

  async generateJWT(email: string, role: string) {
    return this.jwtService.sign({ email, role });
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Implementing Crypto.js in the Frontend (Next.js)

To enhance security, we encrypt credentials before sending them to the backend.

2.1 Install Crypto.js

npm install crypto-js
Enter fullscreen mode Exit fullscreen mode

2.2 Encrypt User Credentials in Next.js

Modify the login page (pages/login.js):

import CryptoJS from 'crypto-js';

async function login(email, password) {
  const encryptedPassword = CryptoJS.AES.encrypt(password, process.env.NEXT_PUBLIC_SECRET).toString();

  const response = await fetch('/api/auth/login', {
    method: 'POST',
    body: JSON.stringify({ email, password: encryptedPassword }),
  });

  const data = await response.json();
  console.log(data);
}
Enter fullscreen mode Exit fullscreen mode

Step 3: JWT-Based Authentication & Role-Based Access

3.1 Protect Routes Using JWT

Middleware for JWT authentication (jwt.strategy.ts):

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET,
    });
  }

  async validate(payload) {
    return { email: payload.email, role: payload.role };
  }
}
Enter fullscreen mode Exit fullscreen mode

3.2 Role-Based Authorization Middleware

To allow access based on user roles, create a decorator:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';

@Injectable()
export class RolesGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    return request.user.role === 'admin'; // Only admins can access
  }
}
Enter fullscreen mode Exit fullscreen mode

Apply the RolesGuard to protected routes:

@UseGuards(JwtAuthGuard, RolesGuard)
@Get('admin')
adminAccess() {
  return "You have admin privileges!";
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Implementing Forgot Password with OTP & NodeMailer

4.1 Generate OTP and Send Email

Install NodeMailer:

npm install nodemailer
Enter fullscreen mode Exit fullscreen mode

Create the email service:

import * as nodemailer from 'nodemailer';

@Injectable()
export class MailService {
  private transporter = nodemailer.createTransport({
    service: 'Gmail',
    auth: {
      user: process.env.EMAIL_USER,
      pass: process.env.EMAIL_PASS,
    },
  });

  async sendOTP(email: string, otp: string) {
    await this.transporter.sendMail({
      from: '"Security Team" <no-reply@example.com>',
      to: email,
      subject: 'Password Reset OTP',
      text: `Your OTP is ${otp}`,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

4.2 Store OTP in Database and Verify

Modify the AuthService:

import { v4 as uuidv4 } from 'uuid';

async requestPasswordReset(email: string) {
  const otp = uuidv4().substring(0, 6); // Generate a 6-digit OTP
  await this.userRepository.update({ email }, { otp });
  await this.mailService.sendOTP(email, otp);
}

async verifyOTP(email: string, otp: string, newPassword: string) {
  const user = await this.userRepository.findOne({ email });

  if (user.otp === otp) {
    user.password = await this.hashPassword(newPassword);
    user.otp = null;
    await this.userRepository.save(user);
    return 'Password reset successfully';
  } else {
    throw new Error('Invalid OTP');
  }
}
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

This authentication system ensures security at every layer:

🔒 Crypto.js encrypts credentials on the frontend.

🔒 bcrypt hashes passwords before storing in the database.

🔒 JWT tokens provide secure access.

🔒 Role-based access restricts sensitive data.

🔒 OTP-based password reset improves security.

With Next.js and Nest.js, we achieve a highly scalable, secure authentication system. 🚀


Comments 0 total

    Add comment