Reputation: 31
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
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
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
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
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