API with NestJS #56. Authorization with roles and claims

JavaScript NestJS TypeScript

This entry is part 56 of 121 in the API with NestJS

So far, in this series, we’ve implemented authentication. By doing that, we can confirm that the users are who they claim to be. In this series, we explain how to implement authentication with JWT tokens or with server-side sessions. We also add two-factor authentication.

While authorization might at first glance seem similar to authentication, it serves a different purpose. With authorization, we check the user’s permission to access a specific resource. A good example would be to allow a user to create posts but not delete them. This article presents two different approaches to authorization and presents how to implement them with NestJS.

While authorization is a separate process, it makes sense to have the authentication mechanism implemented first.

Role-based access control (RBAC)

With role-based access control (RBAC), we assign roles to users. Let’s create an enum containing fundamental roles:

role.enum.ts

We also need a column to define the role of a particular user. Since in this series we use PostgreSQL, we can use the enum type:

Fortunately, TypeORM supports enums. Let’s add it to the definition of the entity. We can make it an array to support the user having multiple roles.

user.entity.ts

Assigning roles to routes

The official NestJS documentation suggests using two separate decorators: one to assign a role to the route and the second one to check if the user has the role. We can simplify that by creating a guard that accepts a parameter. To do that, we need to create a mixin.

role.guard.ts

An important thing above is that we use the interface. For the request to contain the property, we also need to use the :

We implement in API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies

posts.controller.ts

If the user does not have the role, NestJS throws 403 Forbidden:

Extending the JwtAuthenticationGuard

The crucial thing about the above code is the correct order of guards. Since decorators run from bottom to top, we need to use the below the .

To deal with the above issue more elegantly, we can extend our :

role.guard.ts

Because we call , we no longer need to use both and :

posts.controller.ts

Claims-based authorization

When implementing claims-based authorization, we take a slightly different approach. Instead of defining a few roles, we define multiple permissions.

permission.enum.ts

Unfortunately, storing all permissions in a single enum might not be a scalable approach. Because of that, we can create multiple enums and merge them.

If you want to read more about merging enums, check this answer on StackOverflow.

categoriesPermission.enum.ts

postsPermission.enum.ts

permission.type.ts

Thanks to the above approach, we can use both as a type and as a value.

Let’s use the above type in the user’s entity definition:

user.entity.ts

Doing the above with TypeORM creates an enum that consists of all of the permissions we’ve defined.

We can use the type to create the :

permission.guard.ts

Above, we extend the to avoid having to use two guards in the same way we’ve done with the .

The last step is to use the on a route:

Summary

In this article, we’ve implemented both role-based and claims-based authorization. We’ve done that by defining guards using the mixin pattern. We’ve also learned about the enum type built into PostgreSQL. While learning about authorization, we’ve used two different approaches. While both role-based and claims-based authorization would work, the latter is more customizable. As our application grows, we might find that it is easier to use claims because they are more generic.

Series Navigation<< API with NestJS #55. Uploading files to the serverAPI with NestJS #57. Composing classes with the mixin pattern >>
Subscribe
Notify of
guest
11 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Roy B
Roy B
2 years ago

Amazing! keep up the good work!

Thanh La
Thanh La
2 years ago

Thank for always support the NestJS community 🙂

Mitch
Mitch
2 years ago

Missing the situation then admin can delete all entities but user can edit/delete only own entities.

load in PermissionGuardo the entity and check owner

Mitch
Mitch
2 years ago

it is loaded twice when load entity in PermissionGuardo an in service. for that it is need to cache load once and return cache for second load


Ade
Ade
2 years ago

Good job please create tutorial on referral system , refferal and refferee , immediately a user register they should receive an email that contain their referral link and they can share the link with their friends to use their link , user can register with link or without link. The highest user with the most refferal count win.

Ilkka Gustafsson
Ilkka Gustafsson
2 years ago

Thanks, this is great stuff! Any chance to get this same topic, but with mongoose?

Dmitry
Dmitry
2 years ago

There is in role.guard.ts

But user doesn’t have a roles field, only role field and it’s not an array.
May be it must be something like?

Sorry if I’m missing something

Dimitrios
Dimitrios
2 years ago

What if we want “minimum” role? Like I’m Role.Owner, but need to access a Role.Admin resource. It won’t match, but admin permissions are included.

jaytonic
jaytonic
2 years ago

Thank you again :)! How would you implement a claim-based approach per ressource? I mean to allow only the author of a post to edit/delete it? I’m doing it “manually” actually, but I wonder if there would be some Guard to implement this in a better way?

Nintendo
2 years ago

Great job. Will be updated to newer versions of NestJS?