DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Nestjs Series- Guards
Vignesh Pugazhendhi
Vignesh Pugazhendhi

Posted on

Nestjs Series- Guards

Introduction

Guards! As the name suggests,it guards something from being accessible without permissions. Guards are a common concept in most backend frameworks,be it provided by the underlying framework or custom coded by the developer. Nestjs makes it simple for us to protect and safe guard apis from unauthorized or unauthenticated users.

Like pipes and filters, guards in nestjs are decorated with @Injectable() decorator.Every guard you use must implement the CanActivate interface. The CanActivate interface properties make it easy developers to custom code their own guard logic.

Let's understand the difference between a middleware and a guard to protect routes. A middleware is completely unaware of what is to be executed after it. A guard on the other hand has access to the ExecutionContext instance and thus knows what is to be executed exactly after it.They are much like filters and pipes and can interpose the correct logic at the correct time in a Request-Response cycle.This property proves that a middleware is dumb.

Guards are executed after each middleware and before and pipes or interceptors.

Let's understand whatever is said with an example (the below code snippet is taken from nestjs official docs):

@Injectable()
export class AuthGuard implements CanActivate{
canActivate(context:ExecutionContext):boolean|Promise<boolean>|Observable<boolean>{
   const request=context.switchToHttpRequest().getRequest();
   //code to validate the request object for roles and 
   //restrictions
}
}
Enter fullscreen mode Exit fullscreen mode

Going through the snippet, most of you must have understood the use of ExecutioContext.But what if one is wants to get the websocket connection context or a gql execution context. ExecutionContext covers all of them. All you need to do is switch to the appropriate context of your need and manipulate the logic. The ExecutionContext class extends the ArgumentsHost, providing you with the right methods to switch between the contexts. You can checkout the official docs as per your needs as this is out of the scope of this article.

Let's talk about binding these guards now. As with pipes, filters and interceptors, guards can be controller-scoped,method-scoped or globally-scoped.Below we use a guard at the controller-scope level using @UseGuards() decorator.

@Controller('pokemons')
@UseGuards(AuthGuard)
export class PokemonController{}
Enter fullscreen mode Exit fullscreen mode

We have passed the class name of the Guard to the decorator. You can even pass and instance to the Guard or even a list of instances or types.

Setting up roles

Roles are a way to tell the controller methods to allow the request-response cycle to complete.If a particular role is not authorized to access an endpoint, the request-response cycle is ended here by returning an unauthorized error message, typically with a 401 HTTP statuscode. Our guards are very smart but they do not know which roles are allowed for which endpoints.This is where custom metadata comes into play. With custom metadata, we can segregate endpoints based on the roles as shown below:

@Post("/updateAccess") 
@SetMetadata('roles',['admin','superadmin'])
async updateReadWriteAccessofUser(@Body() inputDto:any):Promise<boolean>{
  this.adminService(inputDto);
}
Enter fullscreen mode Exit fullscreen mode

Now we have assigned the roles to the updateReadWriteAccessOfUser method. Only the user with "admin" and "superadmin" roles can access this endpoint "/updateAccess".While this is enough for this concept to be understood, it is not a good practice to assign roles directly on the controller methods. Instead we can code our own decorator for this and use it. We code this such that it follows DRY solid principle.

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

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

Enter fullscreen mode Exit fullscreen mode

Now you can reuse this decorator whereever needed.

@Post("/updateAccess") 
@Roles(["admin","superadmin"])
async updateReadWriteAccessofUser(@Body() inputDto:any):Promise<boolean>{
  this.adminService(inputDto);
}
Enter fullscreen mode Exit fullscreen mode

Now we combine the concepts of roles and guards to protect our endpoint from unauthorized requests.

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 roles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return verifyRoles(roles, user.roles);
  }
}

export function Roles(...roles: string[]) {
    return applyDecorators(
        SetMetadata('roles', roles),
        UseGuards(RolesGuard),
    );
}


Enter fullscreen mode Exit fullscreen mode

The Reflector helper class is provided by the nestjs framework to access the controller method's roles. We verify the roles the current request's roles and return a boolean based on the verification. In the second code snippet, we make use of this RolesGuard as a paramter to the applyDecorators. The applyDecorators method combines multiple decorators and executes them.

Any exception thrown by a guard will be handled by the exceptions layer (global exceptions filter and any exceptions filters that are applied to the current context).

Top comments (0)

An Animated Guide to Node.js Event Loop

>> Check out this classic DEV post <<