I am just trying to implement my Roles Decorator in Nestjs. So far it went pretty well, until I wanted to compare the user of the payload data from the jwt-token to the required role. I can't explain how this is possible, but it is only possible to get the user and role out of the payload data, if i let the function return always true. After I set a condition the user is undefined. How is this even possible?
Here is my (shortened) user controller, where I use the Decorator:
@UseGuards(JwtAuthGuard, RolesGuard)
@Controller('users')
export class UsersController {
constructor(private readonly userService: UserService) {}
private readonly logger = new Logger(LoggingService.name);
@Post('/create')
async create(@Body() createUserDto: CreateUserDto) {
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
createUserDto.password = hashedPassword
return this.userService.createUser(createUserDto);
}
@Roles(Role.SUPERADMIN)
@Get('/')
showUsers() {
return this.userService.getUsers();
}
And here is the roles decorator, with condition:
import { CanActivate, ExecutionContext, Injectable, Logger } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { LoggingService } from 'src/services/logging/logging.service';
import { User } from 'src/user/schemas/user.schema';
import { Role } from '../role.enum';
import { ROLES_KEY } from '../decorators/roles.decorator';
import { Types } from 'mongoose';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
private readonly logger = new Logger(LoggingService.name);
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const roles = this.reflector.get<Role[]>(ROLES_KEY, context.getHandler());
// If there is no Roles-Decorator, just pass through
if (!roles) {
return true;
}
this.logger.debug("REQUIRED ROLES: ", roles)
const request = context.switchToHttp().getRequest();
const userRole = request.user?.roles;
const userID = request.user?.sub;
this.logger.debug("ROLES GUARD USER", userID);
this.logger.debug("USER ROLE", userRole);
// Else, check the request header if it matches
if (roles.includes(userRole)) {
return true;
} else { return false }
}
}
Logging output (when I try to access the route):
[Nest] 4460 - 25.11.2021, 18:56:33 DEBUG [LoggingService] REQUIRED ROLES:
[Nest] 4460 - 25.11.2021, 18:56:33 DEBUG [LoggingService] superadmin
[Nest] 4460 - 25.11.2021, 18:56:33 DEBUG [LoggingService] ROLES GUARD USER
[Nest] 4460 - 25.11.2021, 18:56:33 DEBUG [LoggingService] undefined
[Nest] 4460 - 25.11.2021, 18:56:33 DEBUG [LoggingService] USER ROLE
[Nest] 4460 - 25.11.2021, 18:56:33 DEBUG [LoggingService] undefined
But when I do this:
// Else, check the request header if it matches
// if (roles.includes(userRole)) {
// return true;
// } else { return false }
return true;
The logging output is this:
[Nest] 39888 - 25.11.2021, 19:00:14 DEBUG [LoggingService] REQUIRED ROLES:
[Nest] 39888 - 25.11.2021, 19:00:14 DEBUG [LoggingService] superadmin
[Nest] 39888 - 25.11.2021, 19:00:14 DEBUG [LoggingService] ROLES GUARD USER
[Nest] 39888 - 25.11.2021, 19:00:14 DEBUG [LoggingService] 619962ad86e412dc06983a0e
[Nest] 39888 - 25.11.2021, 19:00:14 DEBUG [LoggingService] USER ROLE
[Nest] 39888 - 25.11.2021, 19:00:14 DEBUG [LoggingService] superadmin
The control-flow is top to bottom, right? Any help would be greatly appreciated!
CodePudding user response:
Using APP_GUARD
in any module that is registered by the application registers the guard as a global guard, that will run before every request. What I would do is register two global guards, one for the JwtAuthGuard
one for the RolesGuard
, and implement a @JwtSkip()
decorator, that tells the JwtAuthGuard
that it can skip authentication on this route (would be the same for the RolesGuard
as it shouldn't have any roles associated with it
CodePudding user response:
I found the Solution!
I had not registered the RolesGuard in a global context. So I went ahead and put
app.useGlobalGuards(new RolesGuard(new Reflector()));
in the main.ts. It then suddenly worked. And I modified the condition to pass the Guard as follows:
// Else, check the request header if it matches
if (roles.some(r => r == userRole)) {
this.logger.log("ROLES GUARD: PASS")
return true;
} else {
this.logger.error("ROLES GUARD: DENIED")
return false
}
Hope this helps anyone!