How to Implement RBAC Authorization in a NestJS Application
- Share:
TL;DR: Enhance the security of your Nest.js API endpoints with Permit.io — an authorization-as-a-service provider, ensuring 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 designed to allow implementing access control into your application - so you don't have to build it from scratch.
In this guide, we'll use Permit.io to implement basic Role Based Access Control (RBAC).
Integrating Permit.io into your Nest.js project can help you ensure that only authorized users can access your API endpoints, protecting sensitive data and preventing unauthorized actions.
What we will cover:
- Setting up a Nest.js Project
- Working with Nest.js Passport library
- Writing a decorator guard
- Setting up our first policy with Permit
- Installing and Initializing Permit
- 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
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
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
Next, we’ll start the development server and see our blog application in action. Run the following command:
npm run start:dev
Navigate to http://localhost:3000
and you should see the below screen. Congratulations, you have set up a Nest.js project!
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
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
.
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;
}
}
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.';
}
}
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:
Now, let’s change the outcome of the Guard logic to return true
.
const userHasPermission = true;
Now we should have access to the protected
route.
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 to manage your first new role:
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.
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.
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.
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.
Let’s quickly add another role — we’ll call it Manager
.
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.
Finally, and most importantly, let’s check the permission to view the page for the Admin role.
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
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]",
});
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.
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;
}
}
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 permission to access the page — let’s call the /protected
route and see.
Indeed, we do have access. Let’s now change the role for our user to Manager
and see if we suddenly lose access.
Alright, our user is now a manager, so let’s navigate to the endpoint path and see.
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.
Want to learn more about Authorization? Join our Slack community, where thousands of developers discuss, build, and implement access control for their applications.
Further Reading:
Written by
Filip Grebowski
Developer Advocate at Permit.io, Software Engineer, and YouTube Creator