Search code examples
enumsnestjsrbac

Undefined user.roles with NestJS RoleGuard


I've created the RoleGuard by following the official NestJs documentation. However, I keep getting a 403 error even if my users have the correct Role.

app.module.ts

@Module({
  imports: [
    MongooseModule.forRoot(process.env.MONGO_DB_URI),
    AuthModule,
    UserModule,
    ProductModule,
    StoreModule,
    OrderModule,
  ],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: APP_GUARD,
      useClass: JwtAuthGuard,
    },
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {}

According to the documentation:

role.enum.ts

export enum Role {
  BUYER = 'buyer',
  SELLER = 'seller',
  ADMIN = 'admin',
}

roles.decorators.ts

import { SetMetadata } from '@nestjs/common';
import { Role } from './role.enum';

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

roles.guard.ts

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

@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));
  }
}

I called a route with @Roles(Role.BUYER) and logged the user and user.roles to get { userId: undefined, email: '[email protected]' } and undefined.

My user entity:

user.entity.ts

import { Document } from 'mongoose';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Role } from 'src/auth/roles/role.enum';

export type UserDocument = UserEntity & Document;

@Schema({
  collection: 'users',
  timestamps: true,
  versionKey: false,
})
export class UserEntity {
  @Prop({ required: true })
  username: String;

  @Prop({ required: true })
  password: String;

  @Prop({ required: true })
  email: String;

  @Prop({ required: true })
  telephone: String;

  @Prop({ required: true, type: String, enum: Role, default: Role.BUYER })
  roles: Role[];

  @Prop()
  storeId: String;
}

export const UserSchema = SchemaFactory.createForClass(UserEntity);

EDIT:

jwt.strategy.ts

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: jwtConstants.secret,
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, email: payload.email };
  }
}

Solution

  • Your JwtStrategy does not return a roles property, so req.user.roles will always be undefined. You need to return a roles property as a part of the JwtStrategy#validate method to read req.user.roles later on