Search code examples
node.jsnestjsnestjs-passport

Nestjs roles guard invokes 2 times and gets user undefined at first


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.

roles guard

app module

usage


Solution

  • 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.