I am currently implementing JWT authentication with Passport.js into a NestJS application.
In some of my GraphQL resolvers I need to access the currently authenticated user. I know that passport will attach the authenticated user to the request object (at least I hope that this is correct), but I do not know how to access the request object inside a resolver.
I followed the issue https://github.com/nestjs/nest/issues/1326 and the mentioned link https://github.com/ForetagInc/fullstack-boilerplate/tree/master/apps/api/src/app/auth inside the issue. I saw some code that uses @Res() res: Request
as a method parameter in the GraphQL resolver methods, but I always get undefined
for res
.
These are the current implementations I have:
GQLAuth
import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
import { AuthenticationError } from 'apollo-server-core';
@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
console.log(req);
return super.canActivate(new ExecutionContextHost([req]));
}
handleRequest(err: any, user: any) {
if (err || !user) {
throw err || new AuthenticationError('GqlAuthGuard');
}
return user;
}
}
Resolver that needs to access the current user
import { UseGuards, Req } from '@nestjs/common';
import { Resolver, Query, Args, Mutation, Context } from '@nestjs/graphql';
import { Request } from 'express';
import { UserService } from './user.service';
import { User } from './models/user.entity';
import { GqlAuthGuard } from '../auth/guards/gql-auth.guard';
@Resolver(of => User)
export class UserResolver {
constructor(private userService: UserService) {}
@Query(returns => User)
@UseGuards(GqlAuthGuard)
whoami(@Req() req: Request) {
console.log(req);
return this.userService.findByUsername('aw');
}
}
JWT Strategy
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';
import { JwtPayload } from './interfaces/jwt-payload.interface';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.SECRET,
});
}
async validate(payload: JwtPayload) {
const user = await this.authService.validateUser(payload);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Authorization and creating JWT tokens works fine. GraphQL guard also works fine for methods that do not need to access the user. But for methods that need access to the currently authenticated user, I see no way of getting it.
Is there a way to accomplish something like this ?
Finally found the answer ... https://github.com/nestjs/graphql/issues/48#issuecomment-420693225 pointed me into the right direction of creating a user decorator
// user.decorator.ts
import { createParamDecorator } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data, req) => req.user,
);
And then use this in my resolver method:
import { User as CurrentUser } from './user.decorator';
@Query(returns => User)
@UseGuards(GqlAuthGuard)
whoami(@CurrentUser() user: User) {
console.log(user);
return this.userService.findByUsername(user.username);
}
Now everything works as expected. So all credits of this answer goes to https://github.com/cschroeter
In order to use an AuthGuard with GraphQL, extend the built-in AuthGuard class and override the getRequest()
method.
Create a file called gql.guard.ts
(Naming your wish)
@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
To get the current authenticated user in your graphql resolver, you can define a @CurrentUser()
decorator (create a file called user.decorator.graphql.ts
)
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req.user;
},
);
To use above decorator in your resolver, be sure to include it as a parameter of your query or mutation
@Query(returns => User)
@UseGuards(GqlAuthGuard)
whoAmI(@CurrentUser() user: User) {
return this.usersService.findById(user.id);
}
Read More : https://docs.nestjs.com/security/authentication#graphql
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With