Node.js Framework Series — 1.2.8. NestJS — Guards

NestJs Architecture — Guards

We can say about the guards that they are the security management layers like Java/Spring Security. We can easily manage all of the authentication and the authorization mechanism, role-based access managements by using guards. There are many different ways of doing it in Node.js, but Guards are developed specially for it. They have a single responsibility, determine whether incoming request will be handled by the router or not according to specific condition (i.e., roles, permission, access control lists…) at run time.

Instead of using Guards, you may ask that why we don’t use middleware. We can still use middleware, but middle ware code will be complex and its reusability will be less. Because the middleware doesn’t know which route works after itself. That’s why, we develop separate injectable class as a guard which is loosely coupled and high cohesion and which has single responsibility.

A guard is a class annotated with the @Injectable() decorator. Guards should implement the CanActivate interface. [1] CanActivate interface force us to implement canActivate() method which can manage our guard logic and returns boolean value. If the response true, it allows the process. If not true, NestJS will deny and throw an exception to the client.

Let’s see how we can use the guards with a simple example. In this example we are going to create a role guard and then we are going to see it’s binding. In general, guards can be controller-scoped, method-scoped and global-scoped like pipes and exception filters.

rbac-roles.guards.ts

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import * as circular from 'circular';
@Injectable()
export class RbacRolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
console.log('RbacRolesGuard controls..');
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
const hasRole = () =>
user.roles.some(role => !!roles.find(item => item === role));
return user && user.roles && hasRole();
}
}

As we see above, we created guard class that implements CanActivate interface which has a canActivate() method. According to our role based access management logic, we can implement our canActivate() method which is going to return TRUE/FALSE. So according to response, we will allow user to get data from the RestAPI’s endpoint or not. Our guard class is a provider, so we can bind it to anywhere via DI(Dependency Injection) pattern.

rbac-roles.decorator.ts

import { SetMetadata } from '@nestjs/common';export const RbacRoles = (...roles: string[]) => SetMetadata('roles', roles);

We don’t need to create a custom decorator as shown above. But this approach is more readable, cleaner and more decoupled. So, we can easily manage our RBAC(Role Based Access Management) on the routes as shown below:

profile.controller.ts

import { RbacRolesGuard } from './../common/guards/rbac-roles.guard';
import {
Body, Controller, Delete, Get, Param, Post, Put, Query, UseGuards,
} from '@nestjs/common';
import { RbacRoles } from 'src/common/decorators/rbac-roles.decorator';
@UseGuards(RbacRolesGuard)
@Controller('profile')
export class ProfileController {
@Get()
@RbacRoles('admin', 'user', 'seller')
findAll(@Query() query) {
return `This endpoint returns all users (limit: ${query.limit} items)`;
}
@Get(':id')
@RbacRoles('admin', 'user', 'seller')
findOne(@Param('id') id: number) {
return `This endpoint returns a #${id} user`;
}
@Get(':userName')
@RbacRoles('admin', 'user', 'seller')
async getUserDetails(@Param('userName') userName: string) {
return 'returns user details';
}
@Put(':id')
@RbacRoles('admin', 'user')
update(@Param('id') id: string, @Body() updateProfileDto) {
return `This endpoint updates a #${id} user`;
}
@Delete(':id')
@RbacRoles('user')
remove(@Param('id') id: string) {
return `This endpoint removes a #${id} user`;
}
}

As you see above, we bind our Guards to controller and to each routes and we applies role based access or deny to them.

We can manage all of our authentication and authorization mechanism and role based access management with Guards.

Resources:

[1] Guards, NestJS Documentation, 2020, available at https://docs.nestjs.com/guards

--

--

Rasim Sen/Blockchain Architect-zero2hero founder

I am a blockchain architect, close on 2 decades of experience as a developer, I’ve got 5 products in blockchain, and I like to talk about tech,sci-fi, futurism!