How to add multiple Nestjs RoleGuards in controllers
Asked Answered
B

2

7

I have role guard for ADMIN, SUPERADMIN, USER, MODERATORS,

This is an example of one of the guards. An Admin guard in the case. They are working as I expected but I can't add multiple guards in the controller

import { Injectable, CanActivate, ExecutionContext, HttpException, HttpStatus } from '@nestjs/common';

@/Injectable()
export class AdminGuard implements CanActivate {
 constructor() { }

 canActivate(context: ExecutionContext) {
 const request = context.switchToHttp().getRequest();
 const user = request.user;

 if (user.usertype == 'Admin') {
 return true;
        }
 throw new HttpException('Unauthorized access', HttpStatus.BAD_REQUEST);
    }
}

in my controllers, I have this decorator

 
@UseGuards(AuthGuard('jwt'), AdminGuard)

I want to be able to do something like this

@UseGuards(AuthGuard('jwt'), AdminGuard, SuperAdminGuard)

or

@UseGuards(AuthGuard('jwt'), [AdminGuard, SuperAdminGuard, UserGuard])

or


@UseGuards(AuthGuard('jwt'), AdminGuard || SuperAdminGuard || UserGuard])

None of the above implementations worked. Is there a better way to go about it? Maybe there is something I am not doing right. I have checked the docs but I can't seem to make it work

Bailsman answered 16/5, 2020 at 10:24 Comment(2)
In @UseGuards(A, B, C), if A returns false, it won't go to B. So in order to proceed, all must return true. You may want to handle like this: if (user.usertype === 'Admin' || user.usertype) { return true } so the next guard can be invoked.Ambulant
Your suggestion worked. I tried it out. But I will go for @kim-kernBailsman
S
8

I suggest to create one generic RolesGuard and define the required roles per controller or route handler with custom metadata:

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const userType = request.user.userType;
    return roles.some(r => r === userType);

  }
}

Custom decorator to set the required roles:

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

How to use it?

Now you can define the required roles as follows:

// For this route you need either Superadmin or Admin privileges
@Roles('Superadmin', 'Admin')
@UseGuards(AuthGuard('jwt'), RolesGuard)
Sundry answered 16/5, 2020 at 12:8 Comment(0)
S
1

Another approach rather than what Kim has answered is to use Mixin. The mixin concept is used by the AuthGuard itself.

export const UserTypeGuard: (...types: string[]) => CanActivate = createUserTypeGuard;

function createUserTypeGuard(...types: string[]) {
    class MixinUserTypeGuard implements CanActivate {
        canActivate(context: ExecutionContext) {
            const user = context.switchToHttp().getRequest().user;
            return types.some(type => user.userType === type);
        }
    }
}

Usage:

@UseGuards(AuthGuard('jwt'), UserTypeGuard('SuperAdmin', 'Admin'))

Snashall answered 16/5, 2020 at 13:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.