facebook-паспорт с NestJS

Я изучил оба passport-facebook а также passport-facebook-tokenинтеграция с NestJS. Проблема в том, что NestJS абстрагирует реализацию паспорта с помощью собственных утилит, таких как AuthGuard.

Из-за этого, ExpressJSЗадокументированная реализация стиля не будет работать с NestJS. Это, например, не соответствует@nestjs/passport пакет:

var FacebookTokenStrategy = require('passport-facebook-token');

passport.use(new FacebookTokenStrategy({
    clientID: FACEBOOK_APP_ID,
    clientSecret: FACEBOOK_APP_SECRET
  }, function(accessToken, refreshToken, profile, done) {
    User.findOrCreate({facebookId: profile.id}, function (error, user) {
      return done(error, user);
    });
  }
));

В этом сообщении блога показана одна стратегия для реализацииpassport-facebook-token используя незнакомый интерфейс, несовместимый с AuthGuard.

@Injectable()
export class FacebookStrategy {
  constructor(
    private readonly userService: UserService,
  ) {
    this.init();
  }
  init() {
    use(
      new FacebookTokenStrategy(
        {
          clientID: <YOUR_APP_CLIENT_ID>,
          clientSecret: <YOUR_APP_CLIENT_SECRET>,
          fbGraphVersion: 'v3.0',
        },
        async (
          accessToken: string,
          refreshToken: string,
          profile: any,
          done: any,
        ) => {
          const user = await this.userService.findOrCreate(
            profile,
          );
          return done(null, user);
        },
      ),
    );
  }
}

Проблема здесь в том, что это кажется совершенно нетрадиционным для того, как NestJS ожидает от вас реализации паспортной стратегии. Это взломано вместе. Это может сломаться и в будущих обновлениях NestJS. Здесь также нет обработки исключений; У меня нет возможности фиксировать исключения, такие какInternalOAuthError который бросает passport-facebook-token из-за используемого характера обратного вызова.

Есть ли чистый способ реализовать любой из passport-facebook или passport-facebook-token так что он будет использовать @nestjs/passportс validate()метод? Из документации: для каждой стратегии Passport будет вызывать функцию проверки (реализованную с помощью метода validate() в @nestjs/ паспорте). Должен быть способ пройтиclientId, clientSecret в конструкторе, а затем поместите остальную логику в validate() метод.

Я бы предположил, что окончательный результат будет выглядеть примерно так (это не работает):

import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import FacebookTokenStrategy from "passport-facebook-token";


@Injectable()
export class FacebookStrategy extends PassportStrategy(FacebookTokenStrategy, 'facebook')
{

    constructor()
    {
        super({
            clientID    : 'anid',     // <- Replace this with your client id
            clientSecret: 'secret', // <- Replace this with your client secret
        })
    }


    async validate(request: any, accessToken: string, refreshToken: string, profile: any, done: Function)
    {
        try
        {
            console.log(`hey we got a profile: `, profile);

            const jwt: string = 'placeholderJWT'
            const user = 
            {
                jwt
            }

            done(null, user);
        }
        catch(err)
        {
            console.log(`got an error: `, err)
            done(err, false);
        }
    }

}

В моем конкретном случае меня не интересует callbackURL. Я просто проверяю токен доступа, который клиент отправил на сервер. Я просто поставил вышесказанное для ясности.

Также, если вам интересно, приведенный выше код создает InternalOAuthErrorно у меня нет возможности зафиксировать исключение в стратегии, чтобы увидеть, в чем реальная проблема, потому что она реализована неправильно. Я знаю, что в данном конкретном случаеaccess_tokenЯ передаю недействительный, если я передаю действительный, код работает. Однако при правильной реализации я мог бы зафиксировать исключение, проверить ошибку и выдать пользователю соответствующее исключение, в данном случае HTTP 401.

InternalOAuthError: Failed to fetch user profile

Кажется очевидным, что исключение выбрасывается за пределами validate() метод, и поэтому наш блок try/catch не захватывает InternalOAuthError. Обработка этого исключения имеет решающее значение для нормального взаимодействия с пользователем, и я не уверен, как NestJS обрабатывает его в этой реализации или как следует выполнять обработку ошибок.

2 ответа

Решение

Вы на правильном пути с Strategy с помощью extends PassportStrategy()настройка класса у вас есть. Чтобы отловить ошибку в паспорте, можно продлитьAuthGuard('facebook') и добавить в handleRequest(). Вы можете узнать больше об этом здесь или взглянуть на этот фрагмент из документации:

import {
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  canActivate(context: ExecutionContext) {
    // 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;
  }
}

Да, здесь используется JWT вместо Facebook, но основная логика и обработчик одинаковы, поэтому он должен работать на вас.

В моем случае я использовал со старой версией гнезда. Для апгрейда понадобилась корректировка стратегии. Меня также не интересует URL-адрес обратного вызова.

Это рабочая версия с passport-facebook-tokenкоторый использует соглашения о вложении и выигрывает от внедрения зависимостей:

      import { Injectable } from '@nestjs/common'

import { PassportStrategy } from '@nestjs/passport'
import * as FacebookTokenStrategy from 'passport-facebook-token'

import { UserService } from '../user/user.service'
import { FacebookUser } from './types'

@Injectable()
export class FacebookStrategy extends PassportStrategy(FacebookTokenStrategy, 'facebook-token') {
  constructor(private userService: UserService) {
    super({
      clientID: process.env.FB_CLIENT_ID,
      clientSecret: process.env.FB_CLIENT_SECRET,
    })
  }

  async validate(
    accessToken: string,
    refreshToken: string,
    profile: FacebookTokenStrategy.Profile,
    done: (err: any, user: any, info?: any) => void,
  ): Promise<any> {
    const userToInsert: FacebookUser = {
      ...
    }

    try {
      const user = await this.userService.findOrCreateWithFacebook(userToInsert)

      return done(null, user.id) // whatever should get to your controller
    } catch (e) {
      return done('error', null)
    }
  }
}

Это создает facebook-tokenкоторые можно использовать в контроллере.

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