5-implementing-role-based-access-control-with-jwt-in-a-nestjs-application.html

Implementing Role-Based Access Control with JWT in a NestJS Application

In today’s digital landscape, security is paramount, especially as applications scale and handle sensitive user data. One effective strategy to enhance security is implementing Role-Based Access Control (RBAC). In this article, we will explore how to implement RBAC using JSON Web Tokens (JWT) in a NestJS application, ensuring that your application is both secure and maintainable.

Understanding Role-Based Access Control (RBAC)

RBAC is a method for regulating access to computer or network resources based on the roles of individual users within an organization. Here’s how it works:

  • Roles: Defined sets of permissions assigned to users based on their job responsibilities.
  • Users: Individuals who have roles assigned to them, which dictate their access levels.
  • Permissions: Specific actions that users can perform, such as reading, writing, or executing.

Why Use RBAC?

Implementing RBAC can help:

  • Enhance Security: By limiting access, you reduce the risk of unauthorized actions.
  • Simplify Management: Easily manage user permissions by grouping them into roles.
  • Improve Compliance: Meet regulatory requirements by controlling user access to critical data.

Setting Up a NestJS Application

Before we dive into the implementation, let's set up a basic NestJS application. If you haven't already, make sure to install the NestJS CLI and create a new project:

npm i -g @nestjs/cli
nest new jwt-rbac-example
cd jwt-rbac-example

Installing Required Packages

To implement JWT and RBAC, we'll need the following packages:

npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcryptjs
npm install --save-dev @types/passport-jwt

Implementing JWT Authentication

Step 1: Create the Authentication Module

First, create an authentication module to handle user login and token generation.

nest g module auth
nest g service auth
nest g controller auth

Step 2: User Entity and Service

Define a User entity and a simple service to simulate user fetching. For simplicity, we will hard-code users with roles.

// src/auth/user.entity.ts
export class User {
  id: number;
  username: string;
  password: string;
  role: string; // e.g., 'admin', 'user'
}

// src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { User } from './user.entity';

@Injectable()
export class AuthService {
  private readonly users: User[] = [
    { id: 1, username: 'admin', password: 'admin', role: 'admin' },
    { id: 2, username: 'user', password: 'user', role: 'user' },
  ];

  async validateUser(username: string, password: string): Promise<User | null> {
    const user = this.users.find(
      (user) => user.username === username && user.password === password,
    );
    return user || null;
  }
}

Step 3: Generating JWT Tokens

Next, use the JwtService to generate a token upon successful login.

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

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

  async login(user: User) {
    const payload = { username: user.username, role: user.role };
    return {
      access_token: this.jwtService.sign(payload),
    };
  }
}

Step 4: Creating the Auth Controller

Now, create an endpoint for user login in the Auth controller.

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

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

  @Post('login')
  async login(@Body() user: { username: string; password: string }) {
    const validatedUser = await this.authService.validateUser(user.username, user.password);
    if (!validatedUser) {
      throw new Error('Invalid credentials');
    }
    return this.authService.login(validatedUser);
  }
}

Implementing Role-Based Access Control

Step 5: Create a Role Guard

Next, create a guard that checks a user's role against the required role.

// src/auth/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';

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

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

    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.split(' ')[1];
    const user = this.jwtService.verify(token);

    return requiredRoles.includes(user.role);
  }
}

Step 6: Decorator for Roles

Create a custom decorator to set roles on your routes.

// src/auth/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

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

Step 7: Protecting Routes

Now you can protect your routes using the RolesGuard.

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

@Controller('admin')
@UseGuards(RolesGuard)
export class AppController {
  @Get()
  @Roles('admin')
  getAdminData() {
    return { message: 'This is admin data' };
  }
}

Conclusion

Implementing Role-Based Access Control with JWT in a NestJS application not only enhances your security posture but also streamlines user management. By following the steps outlined in this article, you can effectively manage user permissions based on their roles within your application.

Key Takeaways

  • RBAC simplifies access management by assigning roles to users.
  • JWT provides a stateless authentication mechanism, ideal for microservices and scalable applications.
  • NestJS makes it easy to create modular and maintainable code for authentication and authorization.

By integrating these concepts into your NestJS application, you can ensure that your users have appropriate access levels, protecting your application's integrity and data. Happy coding!

SR
Syed
Rizwan

About the Author

Syed Rizwan is a Machine Learning Engineer with 5 years of experience in AI, IoT, and Industrial Automation.