I use passport and want to protect few routes by roles. It looks like guard invokes 2 times. At first it logs user undefined. Second time it logs user correct. What I did wrong? See screenshots of implementation.
The first time that the guard runs comes from the globally bound guard
{
provide: APP_GUARD,
useClass: RolesGuard
}
As this is a global guard, it will be the first guard invoked in the request chain (and because there are no other guards). Your RolesGuard
is looking at the req.user
property, which gets assigned by passport
. In NestJS this is ususally done by the AuthGuard
which will call passport.authenticate()
under the hood. The code there is a bit complex, so trust me on it for now.
Due to how you have your guards bound, as mentioned, the execution looks like RolesGuard (global)
, JwtAuthGuard (route level)
, RolesGuard (route level)
. That second route level guard, your second RolesGuard
runs after the JwtAuthGuard
so it has access to req.user
as you would expect it to.
Now, the next question you'll probably ask is "How can I fix this?" What I've done for this in the past is I've bound my JwtAuthGuard
(or similar) global and ran it before the RolesGuard
, but added in a check for metadata on if the route should be authorization protected or not. Using decorators we can add metadata to classes and route handlers (kind of what Nest is about being able to do in the first place) and read it from the guard thanks to the ExecutionContext
in the canActivate
method. You can inject Nest's Reflector
into the guard class as well and make a check like this.reflector.getAllAndMerge<boolean>('SHOULD_SKIP_AUTH', [context.getHandler(), context.getClass()])
. Theoretically, this would return a boolean that you've set via the decorator on the class or route handler level, if it returns true
(i.e. you should skip the auth) then the request guard could short out to a return true
and let the request move on its own.