TypeError : Cannot read property 'roles' of undefined NestJs

Viewed 1487

I try to block some root if it's not an admin, but when I run the code I have a TypeError but I don't know how to resolve it.

Thanks

roles.guards.ts

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role } from './role.enums';
import { ROLES_KEY } from './roles.decorator';

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

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!requiredRoles) {
      return true;
    }
    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.some((Role) => user.roles?.includes(Role));
  }
}

articles.controllers.ts

@UseGuards(JwtAuthGuard)
@Roles(Role.Admin)
async addArticle(
    @Body('title') artTitle: string,
    @Body('description') artDescription: string,
    @Body('url') artUrl: string,
    @Body('cover') artCover: string,
    @Body('content') artContent: string,
    @Body('category') artCategory: string,
){
    const generatedId = await  this.articlesService.insertArticle(
        artTitle,
        artDescription,
        artUrl,
        artCover,
        artContent,
        artCategory
    );
    return { id: generatedId };
  }

when I run the code I have a TypeError but I don't know how to resolve it.

Thanks

4 Answers

If I had to bet, your RolesGuard is bound to the global scope, whereas the JwtAuthGuard is bound to the route handler scope. Global guards execute first and foremost, so the RolesGuard executes before the JwtAuthGaurd can set req.user (passport is what does this under the hood). What you can do is either ensure that there is a req.user property (either via a middleware or jutt running the JwtAuthGuard globally) or you can move the RolesGuard to be scoped at the route handler level after the JwtAuthGuard runs.

I'd like to add more detail to Jay McDoniel's answer since it still took me a few hours to get around this issue.

  1. Create JWT.module.ts (JwtModule is already used by @nestjs/jwt hence my use of caps) file with the following:
import { ConfigModule, ConfigService } from "@nestjs/config";
import { JwtModule } from "@nestjs/jwt";

@Module({
    imports: [
      {
        ...JwtModule.registerAsync({
            imports: [ConfigModule],
            useFactory: async (configService: ConfigService) => ({
              secretOrKeyProvider: () => (configService.get<string>("JWT_SECRET")),
              signOptions: {
                  expiresIn: 3600,
              },
            }),
            inject: [ConfigService],
          }),
        global: true
      }
    ],
    exports: [JwtModule]
})
export class JWTModule {}
  1. Add this class to your app.module.ts's imports array.
  2. if you have
{
      provide: APP_GUARD,
      useClass: RolesGuard,
},

in any of your modules... DELETE IT. declaring guards in any providers will automatically make it global and endpoints which you don't want to be guarded will end up getting guarded (I'm aware https://docs.nestjs.com/security/authorization#basic-rbac-implementation tells you to register the role guard in your providers but that just didn't work for me). You only need to import your strategies to the relevant routes.

  1. In your controller, this should now work
  @ApiBearerAuth()
  @Roles(Role.Admin)
  @UseGuards(JwtAuthGuard, RolesGuard)
  @Get()
  findAll() {
    return this.usersService.findAll();
  }

so this endpoint accepts users with a valid JWT and an admin role inside said JWT.

Use JwtGuard and RoleGuard in the controller like @UseGuards(JwtAuthGuard, RolesGuard). The issue because of RoleGuards is not used in guard.

@Roles(Role.ADMIN)
@UseGuards(JwtAuthGuard,RolesGuard)
@Query(() => [User], { name: 'User' })

articles.module.ts

in this module file update in provider rolesGuards

  providers: [AuthResolver, AuthService,LocalStrategy,JwtStrategy,RolesGuard],

use @Post() above your controller

@UseGuards(JwtAuthGuard)
@Roles(Role.Admin)
@Post('')
async addArticle(
    @Body('title') artTitle: string,
    @Body('description') artDescription: string,
    @Body('url') artUrl: string,
    @Body('cover') artCover: string,
    @Body('content') artContent: string,
    @Body('category') artCategory: string,
){
    const generatedId = await  this.articlesService.insertArticle(
        artTitle,
        artDescription,
        artUrl,
        artCover,
        artContent,
        artCategory
    );
    return { id: generatedId };
  }
Related