SWASTIJ CONSULTANCY

Nest RBAC NATS

Learn how to implement Role-Based Access Control (RBAC) in NestJS using NATS. Step-by-step guide on roles and permissions with two roles.

Published On 2023-10-15

Nest RBAC NATS

By Swasti Jain

Introduction

In this article, I will show you how to implement RBAC in NestJS using NATS.

We will create a simple application with two roles: admin and user. The admin role will have full access to the system, while the user role will only be able to view data.

But first, let us clear some basics.

Why NATS?

  • It fits the microservice
  • lightweight
  • flexible
  • highly performant
  • considering factors like load balancing and service discovery, it makes it easier to create microservices with NATS.
  • Data shows that for the execution of requests sent serially, `NATS takes much less time than HTTP`.

RBAC

Role-based access control (RBAC) is a method of restricting network access based on the roles of individual users within an enterprise. Organizations use RBAC `also called role-based security` to parse access levels based on an employee's roles and responsibilities.

Role-based system access (RBAC) is a security mechanism that allows you to control who has access to what resources in your system. With RBAC, you can define roles that have different permissions, and then assign users to those roles. This allows you to fine-grained control over who can access your system and what they can do.

Role-Based System Access in NestJS Using NATS

NestJS is a framework for building scalable, efficient, and easy-to-maintain web applications. NATS is a lightweight messaging system that can be used to decouple different parts of your application.

Let's create a simple application with two roles: admin and user. The admin role will have full access to the system, while the user role will only be able to view data.

Getting Started

The first step is to create a new `NestJS` project. We can do this by running the following command:

1nest new project-name


I have used nest-access-control library to implement RBAC in my microservice architecture.

To implement RBAC in NestJS using NATS, you will need to do the following:

  • Define your roles and permissions.
  • Create a user model.
  • Create a role model.
  • Create a service to manage roles and permissions.
  • Create a controller to authenticate users and assign them roles.

Here is an example of how to define roles and permissions:

1//app.roles.ts
2
3import { RolesBuilder } from 'nest-access-control/roles-builder.class';
4
5export enum AppRoles {
6USER = 'user',
7ADMIN = 'admin',
8}
9
10export const roles: RolesBuilder = new RolesBuilder();
11
12roles
13.grant(AppRoles.USER) // define new or modify existing role. also takes an array.
14.createOwn('user')
15.readOwn('user')
16.grant(AppRoles.ADMIN)
17.createAny('user')
18.readAny('user')
19.readAny('business-info')
20.createAny('business-info');

Creating Role Guards and Decorators

Now that we’ve defined our roles and permissions using `RolesBuilder`, the next step is to protect specific routes or actions based on the assigned roles. This is where custom decorators and guards come in.

📌 Step 1: Create a @InjectRolesBuilder( ) Decorator

1//inject-roles-builder.decorator.ts
2
3import { Inject } from '@nestjs/common';
4import { ROLES_BUILDER_TOKEN } from 'nest-access-control/constants';
5
6/**
7* Get access to the underlying `RolesBuilder` Object
8*/
9export const InjectRolesBuilder = () => Inject(ROLES_BUILDER_TOKEN);

🛡️ Step 2: Create a RolesGuard - "ACGuard"

1// acguard.guard.ts
2
3import {
4  Injectable,
5  CanActivate,
6  ExecutionContext,
7  UnauthorizedException,
8} from '@nestjs/common';
9import { Reflector } from '@nestjs/core';
10import { IQueryInfo } from 'accesscontrol';
11import { InjectRolesBuilder } from 'src/decorators/inject-roles-builder.decorator';
12import { Role } from 'nest-access-control/role.interface';
13import { RolesBuilder } from 'nest-access-control/roles-builder.class';
14import UserDb from 'src/mock/user';
15
16@Injectable()
17export class ACGuard<User extends any = any> implements CanActivate {
18  constructor(
19    private readonly reflector: Reflector,
20    @InjectRolesBuilder() private readonly roleBuilder: RolesBuilder,
21  ) {}
22
23  protected async getUser(context: ExecutionContext): Promise<User> {
24    const user = context.switchToRpc().getData().user;
25    return user;
26  }
27
28  protected async getUserRoles(
29    context: ExecutionContext,
30  ): Promise<string | string[]> {
31    const user: any = await this.getUser(context);
32    if (!user) throw new UnauthorizedException();
33
34    const userRole = UserDb.find((u) => u.id === user.id)?.roles;
35    return userRole;
36  }
37
38  public async canActivate(context: ExecutionContext): Promise<boolean> {
39    const roles = this.reflector.get<Role[]>('roles', context.getHandler());
40    if (!roles) {
41      return true;
42    }
43
44    const userRoles = await this.getUserRoles(context);
45
46    const hasRoles = roles.every((role) => {
47      const queryInfo: IQueryInfo = role;
48      queryInfo.role = userRoles;
49
50      const permission = this.roleBuilder.permission(queryInfo);
51      return permission.granted;
52    });
53
54    return hasRoles;
55  }
56}
✅ Don’t forget to register this guard globally or apply it using @UseGuards(RolesGuard) on protected routes.

🧵 Wiring It All Together with NATS

With RBAC now in place at the HTTP layer, let’s talk about how it integrates with your microservices via NATS.

In a microservice architecture, the gateway receives the HTTP request and communicates with other services (like auth, user, product) via NATS.

For example, from the gateway, you could do:

1// src/gateway/app.controller.ts
2
3@Get('/me')
4async getCurrentUser(@Req() req: any, @Response() response: any) {
5  try {
6    const res = await lastValueFrom(
7      this.userClient.send('me', { headers: req.headers }),
8    );
9
10    return response.status(res.code || res.status || 200).json(res);
11  } catch (e) {
12    console.error('error', e);
13
14    return response
15      .status(e.code || e.status || 500)
16      .json(e.message);
17  }
18}

In the user service, you’d have:

1// src/users/users.controller.ts
2
3@UseInterceptors(ClassSerializerInterceptor)
4@UseGuards(AuthMiddleware)
5@MessagePattern('me')
6async getCurrentUser(data: any) {
7  return await this.appService.handleGetCurrentUser(data.user);
8}
Alternatively, depending on your requirements and business logic, you can have the ACGuard at the gateway itself.

Option 2 - ACGuard at gateway

In this case, your gateway will be

1// src/users/users.controller.ts
2
3@Get('me')
4@UseGuards(JwtAuthGuard, RolesGuard)
5@Roles('user', 'admin')
6async getMyUser(@Req() req) {
7  return this.natsClient.send('get-user', req.user.id);
8}// src/users/users.controller.ts
9
10@Get('me')
11@UseGuards(JwtAuthGuard, RolesGuard)
12@Roles('user', 'admin')
13async getMyUser(@Req() req) {
14  return this.natsClient.send('get-user', req.user.id);
15}

and in user service, you'll have

1// src/users/users.service.ts
2
3@MessagePattern('get-user')
4getUser(@Payload() userId: string) {
5  return this.userRepository.findOne({ where: { id: userId } });
6}

🧪 Testing the Flow

Once your services are up, test the full flow:

  • Login as a user or admin → receive JWT
  • Send request to a protected route
  • Gateway/user-service checks role with RolesGuard
  • The request is sent via NATS to the appropriate service
  • Microservice responds and data is returned

You now have a fully functioning, role-based microservice architecture using NestJS and NATS! 🎉