🔑 NestJS Expert Series (Part 3): Authentication & Authorization with JWT and Guards your


Welcome back to the NestJS Expert Series! 🎉
In Part https://dev.to/devto_with_yog/nestjs-expert-series-part-2-database-integration-with-prisma-typeorm-ica, we integrated our NestJS app with a database using Prisma and TypeORM.

Now it’s time to secure our application with Authentication (who you are) and Authorization (what you can do).

In this article, we’ll build a JWT-based authentication system and use Guards to implement role-based access control.


⚡ Why JWT for Authentication?

JWT (JSON Web Token) is a compact, URL-safe token format widely used for stateless authentication.

✅ Stateless – No need to store session data on the server.
✅ Scalable – Works well in distributed systems.
✅ Cross-platform – Can be used by web, mobile, and microservices.


📦 Step 1: Install Required Packages


Enter fullscreen mode

Exit fullscreen mode


🛠️ Step 2: Auth Module Setup

Generate an auth module, service, and controller:


nest g module auth
nest g service auth
nest g controller auth
Enter fullscreen mode

Exit fullscreen mode


👤 Step 3: User Entity (with Prisma Example)

If you’re using Prisma, update your schema:

model User {
  id       Int    @id @default(autoincrement())
  email    String @unique
  password String
  role     String @default("user")
}
Enter fullscreen mode

Exit fullscreen mode

Run migration:

npx prisma migrate dev --name add_user_model
Enter fullscreen mode

Exit fullscreen mode


🔑 Step 4: Auth Service (Register & Login)

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

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

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

  async validatePassword(password: string, hash: string): Promise {
    return bcrypt.compare(password, hash);
  }

  async generateToken(user: { id: number; email: string; role: string }) {
    return this.jwtService.sign({ sub: user.id, email: user.email, role: user.role });
  }
}

Enter fullscreen mode

Exit fullscreen mode


📜 Step 5: Auth Controller

import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';

@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {}

  @Post('register')
  async register(@Body() body: { email: string; password: string }) {
    const hashed = await this.authService.hashPassword(body.password);
    // Save user to DB with hashed password
    return { message: 'User registered successfully' };
  }

  @Post('login')
  async login(@Body() body: { email: string; password: string }) {
    // Find user in DB and validate password
    // If valid, generate token
    return { access_token: await this.authService.generateToken({ id: 1, email: body.email, role: 'user' }) };
  }
}
Enter fullscreen mode

Exit fullscreen mode


🛡️ Step 6: Guards for Authorization

NestJS Guards decide whether a request should proceed or not.

Create a roles.guard.ts:

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

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.get('roles', context.getHandler());
    if (!requiredRoles) return true;

    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.includes(user.role);
  }
}

Enter fullscreen mode

Exit fullscreen mode

Use a custom decorator roles.decorator.ts:

import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

Apply it in a controller:

import { Controller, Get, UseGuards } from '@nestjs/common';
import { Roles } from './roles.decorator';
import { RolesGuard } from './roles.guard';

@Controller('admin')
export class AdminController {
  @Get()
  @Roles('admin')
  @UseGuards(RolesGuard)
  findAdminData() {
    return { secret: 'This is admin-only data' };
  }
}

Enter fullscreen mode

Exit fullscreen mode


🚀 Conclusion

✅ We built JWT-based authentication.

✅ We secured routes with Guards and Role-based Authorization.

✅ We now have the foundation for a secure, multi-user NestJS application.

In the next part, we’ll tackle Validation & Error Handling with Pipes and Filters to make our APIs more robust.


💡 If you found this helpful, drop a ❤️ on Dev.to and follow me for Part 4 of the NestJS Expert Series.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *