NestJS Get current user in GraphQL resolver authenticated with JWT
Asked Answered
W

6

30

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 ?

Whitleywhitlock answered 20/3, 2019 at 20:38 Comment(2)
Instead of implement your own canActivate method in your GqlAuthGuard you should create a getRequest method and return GqlExecutionContext.create(context).getContext().req;. This is a better approach in my opinion.Astrology
Would you share a link to your GitHub repo? I'm new to Nest.js, I'm also using GraphQL and I'm stuck with the authentication implementation. Thanks!Kablesh
W
47

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

Whitleywhitlock answered 20/3, 2019 at 21:21 Comment(3)
This really needs to be part of the framework and in the default docs, something like this missing makes me think if people are actually using it, lolKrummhorn
Thank you! Also, as this is a working answer you should accept it even if it's your own. Thanks again, I searched for a solution for at least an hour before finding this and it worked perfectly.Floriated
In v7 of Nest createParamDecorator has change. Retrieving the user is done through the GraphQL context. See here: docs.nestjs.com/graphql/other-features#custom-decoratorsDicentra
P
12

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

Pardon answered 2/3, 2022 at 16:20 Comment(1)
This worked for me on August, 2022Appointor
G
3

Another approach is to validate web token with whatever package you are using, then create decorator get-user.decorator.ts

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';


export const GetUser = createParamDecorator((data, context: ExecutionContext)  => {
 const ctx = GqlExecutionContext.create(context).getContext();
return ctx.user
});

then in your resolver, you can use this decorator (@GetUser() user: User) to access the user

Gestate answered 4/7, 2020 at 14:25 Comment(0)
R
2

Wish I could take any sort of credit here, I'm simply passing along information from the course, NestJS Zero To Hero (absolutely fantastic btw).

For NestJS 7:

// get-user.decorator.ts

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

import { User } from '../../user/entity/user.entity';

export const GetAuthenticatedUser = createParamDecorator((data, ctx: ExecutionContext): User => {
  const req = ctx.switchToHttp().getRequest();
  return req.user;
});

You can implement this however you like. I have an auth.controller that looks something like this:

// auth.controller.ts

import { GetAuthenticatedUser } from './decarator/get-user.decorator';

...

@Controller('api/v1/auth')
export class AuthController {
  constructor(private authService: AuthService) {
    //
  }

  ...

  /**
   * Get the currently authenticated user.
   *
   * @param user
   */
   @Post('/user')
   @UseGuards(AuthGuard())
   async getAuthenticatedUser(@GetAuthenticatedUser() user: User) {
     console.log('user', user);
   }

Result is something like this:

// console.log output:

user User {
  id: 1,
  email: '[email protected]',
  ...
}
Ridgeway answered 4/10, 2020 at 15:27 Comment(1)
Note that this will only work for REST services. When using GraphQL you will need to make use of the context associated with GraphQL GqlExecutionContext.create(context).getContext() and not ctx.switchToHttp().getRequest()Dicentra
V
0
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

export const CurrentUser = createParamDecorator(
  (data, context: ExecutionContext) => {
    const ctx = GqlExecutionContext.create(context).getContext();
    return ctx.req.user;
  },
);
Valene answered 11/8, 2021 at 19:0 Comment(0)
T
0

This worked for me

export const CurrentUser = createParamDecorator(
  (data, context: ExecutionContextHost) => {
    return GqlExecutionContext.create(context).getContext().req.user;
  },
);
Transpolar answered 23/12, 2023 at 17:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.