DEV Community

Cover image for Role-Based Access Control in NestJS
Anandhu Prakash
Anandhu Prakash

Posted on

Role-Based Access Control in NestJS

Role-Based Authorization in NestJS

Authorization is a critical aspect of application security. It ensures that users can access only the resources they’re permitted to. In NestJS, a progressive Node.js framework, implementing role-based authorization is straightforward thanks to its modular design and support for decorators.

What is Role-Based Authorization?

Role-based authorization assigns permissions to users based on their roles. Common roles include “admin,” “user,” and “guest.” Each role has a predefined set of actions it can perform. For example:

  • Admin: Can manage all resources.
  • User: Can read and update their own data.
  • Guest: Can only view public resources.

By implementing role-based authorization, you can easily control access within your application.

Setting Up Role-Based Authorization in NestJS

Here’s how you can implement role-based authorization in NestJS:

1. Install Dependencies

First, ensure you have a NestJS project set up. If not, you can create one using the CLI:

npm i -g @nestjs/cli
nest new my-app
Enter fullscreen mode Exit fullscreen mode

Next, install @nestjs/passport and passport for authentication support:

npm install @nestjs/passport passport passport-jwt
npm install --save-dev @types/passport-jwt
Enter fullscreen mode Exit fullscreen mode

2. Define Roles

Create a roles.enum.ts file to define roles as an enum:

export enum Role {
  Admin = 'admin',
  User = 'user',
  Guest = 'guest',
}
Enter fullscreen mode Exit fullscreen mode

3. Create a Roles Guard

Guards in NestJS intercept requests before they reach route handlers. Create a custom guard to enforce role-based access control.

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

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

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<Role[]>('roles', context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return roles.some((role) => user?.roles?.includes(role));
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Create a Roles Decorator

To simplify the process of adding roles to route handlers, create a custom decorator:

import { SetMetadata } from '@nestjs/common';
import { Role } from './roles.enum';

export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);
Enter fullscreen mode Exit fullscreen mode

5. Apply the Roles Guard Globally or Locally

To make the RolesGuard active, you can apply it globally or locally. For global application, add it to your AppModule:

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { RolesGuard } from './roles.guard';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

For local application, use it in specific controllers or methods:

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

@Controller('resources')
@UseGuards(RolesGuard)
export class ResourcesController {
  @Get()
  @Roles(Role.Admin, Role.User)
  findAll() {
    return 'This endpoint is accessible to Admins and Users.';
  }
}
Enter fullscreen mode Exit fullscreen mode

6. Authenticate Users

To identify a user’s roles, ensure your authentication process adds the roles to the user object in the request. This often involves decoding a JWT or using a session store.

Example Authentication Middleware

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const token = req.headers['authorization'];
    if (token) {
      const decoded = this.decodeToken(token); // Implement JWT decoding
      req.user = decoded;
    }
    next();
  }

  private decodeToken(token: string): any {
    // Logic to decode JWT and retrieve user data
    return { roles: ['user'] }; // Example decoded payload
  }
}
Enter fullscreen mode Exit fullscreen mode

Testing the Implementation

Start your application and test endpoints with users of different roles. Ensure each user can access only what their role permits.

Example Requests

  • Admin User: Should access all routes.
  • Regular User: Should access user-specific routes.
  • Guest: Should be restricted to public routes.

Conclusion

Role-based authorization in NestJS is clean and efficient thanks to decorators, guards, and the framework’s modularity. By following the steps above, you can secure your application while maintaining flexibility and scalability.

For more complex scenarios, consider integrating with external libraries like Casl or implementing attribute-based access control (ABAC).

Top comments (0)