Melly
Melly

Reputation: 31

Can't retrieve the request.user from my RoleGuard, nestJS

I'm makign a role guard to be used in specefic controllers. I'm following the Documentation way of using it globally in the app.module level. Here is the code.

    async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors();
  app.useGlobalGuards(new RoleGuard(new Reflector()));
  await app.listen(3000);
}

Now the Code for my RoleGuard works fine and returns console.logged results when they're expected, Only that the request.user which should be added by the passport middleware is not existant. Here is the code for the guard:

    export class RoleGuard implements CanActivate {
  constructor(private reflector: Reflector) {}
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!roles) {
      console.log('no roles');
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    console.dir(user);
    return this.matchRoles(roles, user);
  }

The console.dir displays undefined, But when trying to console.dir the request it works fine, only that it doesnt contain the user proprety.

Upvotes: 0

Views: 1234

Answers (4)

Kashyap Solanki
Kashyap Solanki

Reputation: 1

Make sure that RoleGuard is not a Global Guard.

Global Gaurds > Controller Guard > API Endpoint Guard

You can read more about it here: https://docs.nestjs.com/guards#binding-guards

Upvotes: 0

Mahadi Hassan
Mahadi Hassan

Reputation: 1016

You can try following code snippet , its almost similar to your code so i am giving the whole snippet :

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}
  canActivate(context: ExecutionContext): boolean {
    // get the role of the request
    const roles = this.reflector.get<Role[]>("roles", context.getHandler());
    if (!roles) {
      return true;
    }

    // now get the current user
    const request = context.switchToHttp().getRequest();
    const requestData = request;

    return roles.some((role) => requestData.user.role.toLowerCase() === role);
  }
}

For my case Role[] coming from userRole of user model. Here is my usecase:

export enum Role {
  ADMIN = "admin",
  USER = "user",
}

You need to import it from your codebase.

Upvotes: 0

Jay McDoniel
Jay McDoniel

Reputation: 70570

Sounds like you figured out your own problem, that the AuthGuard needs to run before the RolesGuard. The reasoning for this is that passport is responsible for setting up the req.user property, so the AuthGuard (which uses passport under the hood) needs to run first. All Nest enhancers have a defined order they run in as documented in the request-lifecycle page. Global enhancers (those bound via app.useGlobal*() or APP_*) are run first. Then controller level enhancers, and finally method level enhancers.

Something you can do to allow for setting the RolesGuard globally is create a guard that extends AuthGuard() and reads some custom metadata about if the AuthGuard should fire or not. If the metadata exists, return true as a short circuit, and if not, then return super.canActivate() in the custom guard's canActivate method. You can set this metadata similarly to how you set the role metadata, also outlined in the documentation.

Upvotes: 1

Melly
Melly

Reputation: 31

Moving the Guard Decorator under the controller away from the main.ts now shows correctly the request.user object. I would assume that the passport middleware appends the user property later and so it was unseen at the global level of the app. I'd love more insight on this issue because this was just my own assumption

Upvotes: 0

Related Questions