DEV Community

Cover image for How to protect a URL inside a NestJS App using RBAC Authorization
Gabriel L. Manor for Permit.io

Posted on • Originally published at permit.io on

How to protect a URL inside a NestJS App using RBAC Authorization

TL;DR: Enhance the security of your Nest.js API endpoints with Permit.io — a powerful tool for implementing authorization and access control. Ensure only authorized users have access to sensitive data.

Building secure and reliable API endpoints is crucial when developing applications with Nest.js. In this blog post, we will explore how you can enhance the security of your Nest.js APIs using Permit.io — a tool specifically designed for access control. By integrating Permit.io into your Nest.js project, you can ensure that only authorized users can access your API endpoints, protecting sensitive data and preventing unauthorized actions.

What we will cover:

  1. Setting up a Nest.js Project
  2. Working with Nest.js Passport library
  3. Writing a decorator guard
  4. Setting up our first policy with Permit
  5. Installing and Initializing Permit
  6. Writing the enforcement code

You’ll need: The latest Node.js version installed, a simple understanding of Express, and familiarity with Typescript.

Setting up a Nest.js Project

To start, we need to install the Nest.js CLI globally. Open your terminal or command prompt and run the following command:

npm install -g @nestjs/cli
Enter fullscreen mode Exit fullscreen mode

This will install the Nest.js CLI, which provides useful commands for generating modules, controllers, and more. Now, let’s create a new Nest.js project for our nestjs-authz-guard. Navigate to the directory where you want to create your project and run the following command:

nest new nestjs-authz-guard
Enter fullscreen mode Exit fullscreen mode

This command will create a new Nest.js project structure with all the necessary files and folders. Let’s enter this project by running:

cd nestjs-authz-guard
Enter fullscreen mode Exit fullscreen mode

Next, we’ll start the development server and see our blog application in action. Run the following command:

npm run start:dev
Enter fullscreen mode Exit fullscreen mode

Navigate to http://localhost:3000 and you should see the below screen. Congratulations, you have set up a Nest.js project!

nest-1.png

Working with Next.js Passport library

Passport is the most popular node.js authentication library, well-known by the community, and successfully used in many production applications. Integrating this library with a Nest application using the @nestjs/passport module is a rather straightforward process. At a high level, Passport executes a series of steps to:

  • Authenticate a user by verifying their credentials (such as username/password, JSON Web Token [JWT], or identity token from an Identity Provider)
  • Manage the authenticated state (by issuing a portable token, such as a JWT, or creating an Express session)
  • Attach information about the authenticated user to the Request object for further use in route handlers

We will install the passport to create a Decorator Guard for our Authorization logic.

npm install @nestjs/passport
Enter fullscreen mode Exit fullscreen mode

Writing our Nest.js Decorator Guard

To start, let’s create another folder in our /src folder inside the project. We can name this folder auth. Next, inside, let’s create a file called permissions.guard.ts.

nest-2.png

In the guard, we’ll house the logic to determine whether a specific user is granted permission to perform certain actions. In this tutorial, we assume the authentication aspect is already implemented, implying that user identity has been verified and a JWT (JSON Web Token) containing a unique user ID is available.

You can find a guide for implementing secure authentication with Nest.js here.

Next, inside our permissions.guard.ts file, let’s add some logic.

// auth/permissions.guard.ts

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class PermissionsGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();

    // Add the authorization logic here with Permit.io.
    // If the user has the necessary permissions, it will return true.
    // If the user does not have the necessary permissions, 
    // throw an UnauthorizedException.

    const userHasPermission = false; // <- replace this line with your Permit.io logic

    if (!userHasPermission) {
      throw new UnauthorizedException('You do not have the necessary permissions.');
    }
    return true;
  }
}
Enter fullscreen mode Exit fullscreen mode

Currently, we are hard coding the state of the permission. We are always setting it to false. Let’s navigate to our app.controller.ts file and add another API endpoint path which we will try to protect.

import { Controller, Get, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { PermissionsGuard } from './auth/permissions.guard';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  @UseGuards(PermissionsGuard)
  @Get('protected')
  getProtectedEndpoint(): string {
    return 'This is a protected route, but you have access.';
  }
}
Enter fullscreen mode Exit fullscreen mode

Here we imported our PermissionsGuard and added a useGuard decorator.

Then we created another @Get request, where we passed in our endpoint name (protected). We wrapped it with a useGuard() decorator, which means that if we navigate to http://localhost:3000/protected we should see this:

nest-3.png

Now, let’s change the outcome of the Guard logic to return true.

const userHasPermission = true;
Enter fullscreen mode Exit fullscreen mode

Now we should have access to the protected route.

nest-4.png

Hurray! We did it. Now it’s time to add Permit’s authorization logic.

Setting up our first policy with Permit

First, go ahead and create an account at https://app.permit.io. You will be presented with an onboarding, but once you enter your organization name, you can just skip the setup (As we will be going through the same steps here, with a more detailed explanation).

Once in the dashboard, navigate to the policy screen to the role manager. You will be prompted yo manage your first new role:

nest-5.png

Let’s create our first role. Each role will have specific rules associated with it, of what a user can and cannot do. The role we will create is an Admin role. The /protected page will only be accessible to users with the Admin role.

To learn more about building RBAC (Role-based Access Control) policies — check out our guide here.

nest-6.png nest-7.png

Success!

Now let’s navigate to the Users panel — we’ll add a sample user and assign them with the Admin role.

In general, this would be the user and their unique ID that you would get from the JWT (JSON Web Token) upon successful authentication, but for this demo, we will just fake that process and pretend it has already happened.

If you need suggestions on the best Authentication Providers to work with, message us on Slack, and we will be more than happy to suggest some.

You can also check out a guide on adding a user to Permit after successful authentication with Auth0 here.

nest-8.png

You can name the user however you’d like.

Next, we’ll navigate to the Policy panel and create our first policy. First, we need to manage and create our first resource. You will be prompted to do so on the screen.

Let’s call our resource protected-page (because that’s what the user will be trying to gain access to). As for the actions that a user will be able to perform on this resource, we only specified view. If you have more actions a user could perform, you can add them as needed.

nest-9.png nest-10.png

If we now look at the policy editor, we will see a grid-like view where the Admin role contains a protected-page resource, which contains the view action that we can check for.

nest-11.png

Let’s quickly add another role — we’ll all it Manager.

nest-12.png

nest-13.png

As we create another role, it automatically contains the previously defined resources and actions. As you can see, managing permissions and adding on top of them is extremely simple and fast.

nest-14.png

Finally, and most importantly, let’s check the permission to view the page for the Admin role.

nest-15.png

Our RBAC policy is now configured. Time to write some simple code to get this to work!

Installing and Initializing Permit

First, let’s install the npm package.

npm install permitio
Enter fullscreen mode Exit fullscreen mode

Now, let’s import the package and add the Permit instance inside our Guard.

// auth/permissions.guard.ts

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Observable } from 'rxjs';
// Permit package import
import { Permit } from 'permitio';

// This line initializes the SDK and connects your app
//to the Permit.io Cloud PDP.
const permit = new Permit({
    pdp: "https://cloudpdp.api.permit.io",
    // your API Key
    token: "[YOUR_API_KEY]",
});
Enter fullscreen mode Exit fullscreen mode

Make sure to replace the API key with your unique API Key obtained from the Permit dashboard. To do so, click on your name in the dashboard and directly copy the API key from there.

nest-16.png

Writing the enforcement code

It’s now time to add the permit.check() function to our guard! Here is what the code will look like:

// auth/permissions.guard.ts

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Permit } from 'permitio';

// This line initializes the SDK and connects your app
// to the Permit.io Cloud PDP.

const permit = new Permit({
    pdp: "https://cloudpdp.api.permit.io",
    // your API Key
    token: "[YOUR_API_KEY]",
});

@Injectable()
export class PermissionsGuard implements CanActivate {
  async canActivate(
    context: ExecutionContext,
  ): Promise<boolean> {
    const request = context.switchToHttp().getRequest();

    // Add the authorization logic here with Permit.io.
    // If the user has the necessary permissions, return true.
    // If the user does not have the necessary permissions, throw an UnauthorizedException.

    const userHasPermission = await permit.check("demo_user@gmail.com", "view", "protected-page");

    console.log(userHasPermission);

    if (!userHasPermission) {
      throw new UnauthorizedException('You do not have the necessary permissions.');
    }
    return true;
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice that we changed the canActivate function to be async, so that we can await for the permit.check() to return the Promise.

Inside the permit.check() function, we pass three parameters. The unique key/id of the user, which in this case is the email, the action we are performing, and the resource we are performing that action on. Permit will check this against the policy we have defined and return a boolean based on the outcome of the permission check.

Because we assigned the Admin role to our user, we should have the permissions to access the page — let’s call the /protected route and see.

nest-17.png

Indeed, we do have access. Let’s now change the role for our user to Manager and see if we suddenly lose access.

nest-18.png

nest-19.png

nest-20.png

Alright, our user is now a manager, so let’s navigate to the endpoint path and see.

nest-21.png

Woops! It looks like the access has been taken away from us. That’s it! It’s as simple as that :)

Next time you need to protect an API endpoint in Nest.js, it will be a breeze. Learn more about Permit — or access the whole code repository for this project here.

Top comments (2)

Collapse
 
gemanor profile image
Gabriel L. Manor

Does URL protection make sense in Nest AuthZ?

Collapse
 
gemanor profile image
Gabriel L. Manor

How would you implement authorization in NestJS?