2
votes

So, I am confused. I'm slowly getting a grip around NestJS but the way passport works has me baffled.

I followed the tutorial and everything works.

I created a JWT Strategy:

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
    constructor(private prisma: PrismaService) {
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            secretOrKey: 'topSecret51',
        });
    }

    async validate(payload: JwtPayload): Promise<User | null> {
        const { email } = payload;
        const user = await this.prisma.user.findOne({
            where: { email }
        });

        if (!user) {
            throw new UnauthorizedException('Athorisation not provided')
        }
        return user;
    }
}

And defined the module:

@Module({
  imports: [
    PassportModule.register({
      defaultStrategy: 'jwt',
    }),
    JwtModule.register({
      secret: 'topSecret51',
      signOptions: {
        expiresIn: 3600,
      },
    })
  ],
  providers: [UserService, PrismaService, JwtStrategy],
  controllers: [UserController],
  exports: [JwtStrategy, PassportModule],
})
export class UserModule {}

And voila a valid token is issued. What I don't understand is how passport accesses the JwtStrategy. How is passport aware that there is a file in my folder structure that contains a JwtStrategy?

1.) It is not dependency injected by the PassportModule or JWTModule 2.) It is not passed as an argument to any method

Is there some behind-the-scenes magic that looks through all providers and determines if any of them is a sub-class of the argument provided to PassportStrategy?

1

1 Answers

4
votes

There's a lot of magic that goes on with the passport module for NestJS. I will do what I can to explain how it all works, though sometimes it goes even beyond me.

The first thing to note is that each PassportStrategy has to extend the abstract mixin PassportStrategy. This mixin takes in a Strategy from the regular passport package (passport-jwt in this case). This strategy has a default name with it (jwt here), but you can pass a second argument to supply your own name.

Nest does some really cool magic to pass the values from CustomStrategy's constructor's super() call to passports normal registration. It then takes CustomStrategy#validate and applies it to passport's verify callback, which is the reason that the validate method must match passport's expected verify callback.

Now we've done some registration with passport, but for these to be called properly, we need to look into the AuthGuard mixin. Normally, we can pass a strategy to the mixin, and this strategy needs to match a passport strategy's name (like jwt in this case) or it can be set in the PassportModule's defaultStrategy option. From here, the guard does a little bit of magic to call the strategy by name (jwt, google, local, etc) and pass the request, the response, and next from the http context Nest creates. At the end of the call, request.user gets set by the returned value from passports.authenticate.