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 enum Role { User = 'User', Admin = 'Admin', } export default Role;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:CREATE TYPE user_role AS ENUM ('User', 'Admin'); CREATE TABLE users ( id serial PRIMARY KEY, email text UNIQUE, role user_role )Fortunately, TypeORM supports enums. Let’s add it to the definition of the User entity. We can make it an array to support the user having multiple roles. user.entity.ts import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; import Role from './role.enum'; @Entity() class User { @PrimaryGeneratedColumn() public id: number; @Column({ unique: true }) public email: string; @Column({ type: 'enum', enum: Role, array: true, default: [Role.User] }) public roles: Role[] // ... } export default User; 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 import Role from './role.enum'; import { CanActivate, ExecutionContext, mixin, Type } from '@nestjs/common'; import RequestWithUser from '../authentication/requestWithUser.interface'; const RoleGuard = (role: Role): Type => { class RoleGuardMixin implements CanActivate { canActivate(context: ExecutionContext) { const request = context.switchToHttp().getRequest(); const user = request.user; return user?.roles.includes(role); } } return mixin(RoleGuardMixin); } export default RoleGuard;An important thing above is that we use the RequestWithUser interface. For the request to contain the user property, we also need to use the JwtAuthenticationGuard: We implement JwtAuthenticationGuard in API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies posts.controller.ts import { ClassSerializerInterceptor, Controller, Delete, Param, ParseIntPipe, UseGuards, UseInterceptors, } from '@nestjs/common'; import PostsService from './posts.service'; import RoleGuard from '../users/role.guard'; import Role from '../users/role.enum'; import JwtAuthenticationGuard from '../authentication/jwt-authentication.guard'; @Controller('posts') @UseInterceptors(ClassSerializerInterceptor) export default class PostsController { constructor( private readonly postsService: PostsService ) {} @Delete(':id') @UseGuards(RoleGuard(Role.Admin)) @UseGuards(JwtAuthenticationGuard) async deletePost(@Param('id', ParseIntPipe) id: number) { return this.postsService.deletePost(id); } // ... }If the user does not have the Admin 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 JwtAuthenticationGuard below the RoleGuard. To deal with the above issue more elegantly, we can extend our JwtAuthenticationGuard: role.guard.ts import Role from './role.enum'; import { CanActivate, ExecutionContext, mixin, Type } from '@nestjs/common'; import RequestWithUser from '../authentication/requestWithUser.interface'; import JwtAuthenticationGuard from '../authentication/jwt-authentication.guard'; const RoleGuard = (role: Role): Type => { class RoleGuardMixin extends JwtAuthenticationGuard { async canActivate(context: ExecutionContext) { await super.canActivate(context); const request = context.switchToHttp().getRequest(); const user = request.user; return user?.roles.includes(role); } } return mixin(RoleGuardMixin); } export default RoleGuard;Because we call await super.canActivate(context), we no longer need to use both JwtAuthenticationGuard and RoleGuard: posts.controller.ts @Delete(':id') @UseGuards(RoleGuard(Role.Admin)) async deletePost(@Param('id', ParseIntPipe) id: number) { return this.postsService.deletePost(id); } 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 enum Permission { DeletePost = 'DeletePost', CreateCategory = 'CreateCategory' } export default Permission;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 enum CategoriesPermission { CreateCategory = 'CreateCategory' } export default CategoriesPermission; postsPermission.enum.ts enum PostsPermission { DeletePost = 'DeletePost' } export default PostsPermission; permission.type.ts import PostsPermission from '../posts/postsPermission.enum'; import CategoriesPermission from '../categories/categoriesPermission.enum'; const Permission = { ...PostsPermission, ...CategoriesPermission } type Permission = PostsPermission | CategoriesPermission; export default Permission; Thanks to the above approach, we can use Permission both as a type and as a value. Let’s use the above type in the user’s entity definition: user.entity.ts import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; import Permission from './permission.type'; @Entity() class User { @PrimaryGeneratedColumn() public id: number; @Column({ unique: true }) public email: string; @Column({ type: 'enum', enum: Permission, array: true, default: [] }) public permissions: Permission[] // ... } export default User;Doing the above with TypeORM creates an enum that consists of all of the permissions we’ve defined. We can use the Permission type to create the PermissionGuard: permission.guard.ts import { CanActivate, ExecutionContext, mixin, Type } from '@nestjs/common'; import RequestWithUser from '../authentication/requestWithUser.interface'; import JwtAuthenticationGuard from '../authentication/jwt-authentication.guard'; import Permission from './permission.type'; const PermissionGuard = (permission: Permission): Type => { class PermissionGuardMixin extends JwtAuthenticationGuard { async canActivate(context: ExecutionContext) { await super.canActivate(context); const request = context.switchToHttp().getRequest(); const user = request.user; return user?.permissions.includes(permission); } } return mixin(PermissionGuardMixin); } export default PermissionGuard; Above, we extend the JwtAuthenticationGuard to avoid having to use two guards in the same way we’ve done with the RoleGuard. The last step is to use the PermissionGuard on a route:@Delete(':id') @UseGuards(PermissionGuard(PostsPermission.DeletePost)) async deletePost(@Param('id', ParseIntPipe) id: number) { return this.postsService.deletePost(id); } 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. The post API with NestJS #56. Authorization with roles and claims appeared first on Marcin Wanago Blog - JavaScript, both frontend and backend.