NestJS Получить текущего пользователя в преобразователе GraphQL, аутентифицированном с помощью JWT

В настоящее время я внедряю аутентификацию JWT с помощью Passport.js в приложение NestJS.

В некоторых моих преобразователях GraphQL мне нужно получить доступ к текущему аутентифицированному пользователю. Я знаю, что паспорт прикрепит аутентифицированного пользователя к объекту запроса (по крайней мере, я надеюсь, что это правильно), но я не знаю, как получить доступ к объекту запроса внутри резолвера.

Я следил за выпуском https://github.com/nestjs/nest/issues/1326 и упомянутой ссылкой https://github.com/ForetagInc/fullstack-boilerplate/tree/master/apps/api/src/app/auth внутри выпуска. Я видел некоторый код, который использует @Res() res: Request в качестве параметра метода в методах распознавателя GraphQL, но я всегда получаю undefined вместо res.

Это текущие реализации, которые у меня есть:

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.info(req);

    return super.canActivate(new ExecutionContextHost([req]));
  }

  handleRequest(err: any, user: any) {
    if (err || !user) {
      throw err || new AuthenticationError('GqlAuthGuard');
    }
    return 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.info(req);
    return this.userService.findByUsername('aw');
  }
}

Стратегия JWT

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;
  }
}

Авторизация и создание токенов JWT работает нормально. Защита GraphQL также отлично работает для методов, которым не требуется доступ к пользователю. Но для методов, которым нужен доступ к текущему аутентифицированному пользователю, я не вижу способа его получить.

Есть ли способ сделать что-то подобное?

Вместо того, чтобы реализовывать свой собственный метод canActivate в своем GqlAuthGuard, вы должны создать метод getRequest и вернуть GqlExecutionContext.create(context).getContext().req;. На мой взгляд, это лучший подход.

Alfonso M. García Astorga 29.08.2019 23:49

Не могли бы вы поделиться ссылкой на ваш репозиторий GitHub? Я новичок в Nest.js, я также использую GraphQL, и я застрял в реализации аутентификации. Спасибо!

Hermeneutiker 01.05.2020 23:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
22
2
16 483
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Наконец-то нашел ответ... https://github.com/nestjs/graphql/issues/48#issuecomment-420693225 указал мне правильное направление создания пользовательский декоратор

// user.decorator.ts
import { createParamDecorator } from '@nestjs/common';

export const CurrentUser = createParamDecorator(
  (data, req) => req.user,
);

А затем используйте это в моем методе распознавателя:

 import { User as CurrentUser } from './user.decorator';

 @Query(returns => User)
  @UseGuards(GqlAuthGuard)
  whoami(@CurrentUser() user: User) {
    console.info(user);
    return this.userService.findByUsername(user.username);
  }

Теперь все работает как положено. Таким образом, все кредиты этого ответа принадлежат https://github.com/cschroeter.

Это действительно должно быть частью фреймворка, и в документах по умолчанию что-то подобное отсутствует, что заставляет меня задуматься, действительно ли люди его используют, лол.

justin.m.chase 09.06.2019 17:06

Спасибо! Кроме того, поскольку это рабочий ответ, вы должны принять его, даже если он ваш собственный. Еще раз спасибо, я искал решение как минимум час, прежде чем нашел это, и оно сработало отлично.

joseym 11.07.2019 16:30

В v7 Nest createParamDecorator изменился. Получение пользователя выполняется через контекст GraphQL. См. здесь: docs.nestjs.com/graphql/other-features#custom-decorators

Christian Groleau 15.04.2021 16:50

Другой подход — проверить веб-токен с помощью любого пакета, который вы используете, а затем создать декоратор 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
});

то в вашем распознавателе вы можете использовать этот декоратор (@GetUser() user: User) для доступа к пользователю

Хотел бы я получить здесь какую-либо оценку, я просто передаю информацию из курса NestJS Zero To Hero (кстати, абсолютно фантастический).

Для 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;
});

Вы можете реализовать это, как вам нравится. У меня есть auth.controller, который выглядит примерно так:

// 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.info('user', user);
   }

Результат примерно такой:

// console.info output:

user User {
  id: 1,
  email: '[email protected]',
  ...
}

Обратите внимание, что это будет работать только для служб REST. При использовании GraphQL вам нужно будет использовать контекст, связанный с GraphQL GqlExecutionContext.create(context).getContext(), а не ctx.switchToHttp().getRequest()

Christian Groleau 15.04.2021 16:43
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;
  },
);

Другие вопросы по теме