I have followed the nestjs example of how to integrate passport with apollo but it constantly crashes with the following exception when I call a guarded resolver.
Looking into it in detail when the @nestjs/passport auth-guard class is extended, it does not call the getRequest function of the child class, instead it calls the pre-existing one in the class (as if inheritance never took place)
[Nest] 25029 - 05/26/2022, 8:29:40 PM ERROR [ExceptionsHandler] Cannot read property 'logIn' of undefined
TypeError: Cannot read property 'logIn' of undefined
at authenticate (.../node_modules/passport/lib/middleware/authenticate.js:96:21)
at ...node_modules/@nestjs/passport/dist/auth.guard.js:96:3
at new Promise (<anonymous>)
at ...node_modules/@nestjs/passport/dist/auth.guard.js:88:83
at JwtAuthGuard.<anonymous> (...node_modules/@nestjs/passport/dist/auth.guard.js:49:36)
at Generator.next (<anonymous>)
at fulfilled (.../node_modules/@nestjs/passport/dist/auth.guard.js:17:58)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
I have the following setup
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GqlExecutionContext } from '@nestjs/graphql';
@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private readonly reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isGuestAllowed = this.reflector.getAllAndOverride<boolean>(IS_GUEST_ALLOWED_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isGuestAllowed) {
return true;
}
// Add your custom authentication logic here
// for example, call super.login(request) to establish a session.
return super.canActivate(context);
}
handleRequest(err, user, info) {
// You can throw an exception based on either "info" or "err" arguments
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: JwtConstants.secret,
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: `.env.${process.env.NODE_ENV}`,
}),
TypeOrmModule.forRoot(),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
debug: true,
playground: true,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
installSubscriptionHandlers: true,
context: ({ req }) => ({ req }),
}),
RecipesModule,
AuthModule,
UsersModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
@Resolver((of) => Recipe)
export class RecipesResolver {
constructor(private readonly recipesService: RecipesService) {}
@UseGuards(GqlAuthGuard)
@Query((returns) => Recipe)
async recipe(@CurrentUser() user: any, @Args('id') id: string): Promise<Recipe> {
const recipe = await this.recipesService.findOneById(id);
if (!recipe) {
throw new NotFoundException(`Recipe with ID "${id}" not found`);
}
return recipe;
}
}
Using the following package versions.
"dependencies": {
"@nestjs/apollo": "^10.0.12",
"@nestjs/common": "^8.0.0",
"@nestjs/config": "^2.0.1",
"@nestjs/core": "^8.0.0",
"@nestjs/graphql": "^10.0.12",
"@nestjs/jwt": "^8.0.1",
"@nestjs/passport": "^8.2.1",
"@nestjs/platform-express": "^8.0.0",
"@nestjs/typeorm": "^8.0.4",
"apollo-server-express": "^3.8.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"cross-env": "^7.0.3",
"graphql": "^16.5.0",
"graphql-query-complexity": "^0.11.0",
"graphql-subscriptions": "^2.0.0",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"pg": "^8.7.3",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"typeorm": "^0.3.6",
"uuid": "^8.3.2"
},
The issue was making the JwtGuard global (not visible in the source code above). It was getting queued before the JqlGuard which it would seem as if my getRequest was never called (was running a different class but being js there was no clear indication as to which class is actually running).