Implementing Role based Access Control in NestJS

11 / Mar / 2024 by harshit.wadhwa 0 comments

NestJS is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications. A prerequisite for this article is a basic understanding of NestJS. If you have worked on Node.js web applications and want to implement applications using object-based programming, such as Java, NestJS is good to go. The learning curve is also not much if you are familiar with Node.js. In this article, we will learn and implement roles-based access control and how to maintain roles and permissions in NestJS applications. For simplicity, we will consider only three simple static roles as of now and not dynamic roles.

Overview on Roles based Access Control

Access control is an essential element of security that determines who is allowed to access certain data, apps, and resources—and in what circumstances. It basically means that not all users will be authorized to access, see, or manipulate all parts/modules of the applications. Users will be given permission by some super-admin or higher authority to access any system module. Now, let’s see how does Access Control work in NestJS in the following steps:

-> We are considering only three roles in this article’s scope, i.e., Super-Admin, Admin & Viewer.

-> First, we will start by creating a role.enum.ts file. An Enum (short for enumeration) is a special data type in many programming languages representing a distinct set of named values. It is used to create custom data types with a collection of names and their corresponding values, making the code more readable and less error-prone. We will create an enums file to store the roles we will use in the application. Following is the code snippet:

export enum Role { SUPER_ADMIN = 'SUPER_ADMIN', ADMIN = 'ADMIN', VIEWER = 'VIEWER' }

-> Next, we will create roles.decorator.ts file. A decorator is a callable object used to modify (decorate) another callable object, such as functions or classes. For example, a function that adds new functionality to another function without changing its code is a decorator. In our use case, we will create a custom decorator called roles, which will be used in API routes to pass those specific role(s) that are allowed on a particular route. If the user doesn’t have those roles, he will not be allowed to access that particular route. Let’s look at the code snippet on how to create roles decorator:

import { CustomDecorator, SetMetadata } from '@nestjs/common'; import { Role } from '../enums/role.enum'; export const Roles = (...roles: Role[]): CustomDecorator<string> => SetMetadata('roles', roles);

Here, we are setting the metadata with key as `roles` and setting the value as an array of roles coming whenever the decorator function is called on a route with different roles as arguments. We will look into this later in the article.

-> Next, we will create the roles.guard.ts file. Guard classes act as a first line of defense, ensuring that only valid data passes through. They’re like the filters that catch the debris before it clogs up the system. So, in our case roles guard will act as an authorisation layer which will prevent the user from accessing any route if he doesn’t have the concerned role. Let’s look at how to write a guard in the following code snippet:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Role } from '../enums/role.enum'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.getAllAndOverride<Role[]>('roles', [ context.getHandler(), context.getClass(), ]); if (!requiredRoles) {  return true; } const { user } = context.switchToHttp().getRequest();  return requiredRoles.some((role) => user.role === role); } }

Here, we are getting the roles set in the metadata of a particular route and comparing those roles with the user’s role coming from the request object. If the role matches, then we are returning true else false. If the guard returns false, the route will return an unauthorized response.

-> Lastly, we must apply the implemented roles on our API route. Refer to the following code snippet for the same:

@Get('search') @Roles('viewer') @UseGuards(RolesGuard) async searchUser(@Query('queryString') queryString: string): Promise<void> {  return this.userService.searchUser(queryString); }

Here, we apply the viewer role on a particular route and implement the roles guard to check the user role. In the same way, we can apply the rest of the roles, too.

Conclusion

This way, we can implement a simple role-based access control in NestJS applications. I hope you understood and liked it. Tune in for more such techieee blogs in the future 😀

FOUND THIS USEFUL? SHARE IT

Leave a Reply

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