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
которые можно использовать в контроллере.