使用 NestJS 的 facebook-passport
facebook-passport with 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);
});
}
));
This blog post 展示了一种使用不符合 AuthGuard
的陌生界面实现 passport-facebook-token
的策略。
@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 将调用验证函数(使用 @nestjs/passport 中的 validate() 方法实现)。应该有办法在构造函数中传递一个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 的处理方式是什么,或者应该如何处理错误。
您的 Strategy
使用 extends PassportStrategy()
class 设置是正确的。为了捕获来自 passport 的错误,您可以扩展 AuthGuard('facebook')
并向 handleRequest()
添加一些自定义逻辑。您可以 read more about it here,或从文档中查看此片段:
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,但底层逻辑和处理程序是相同的,因此它应该仍然适合您。
在我的例子中,我曾经将 passport-facebook-token
与旧版本的 nest 一起使用。要升级,就需要调整策略。我对回调也不感兴趣 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
。
我研究了 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);
});
}
));
This blog post 展示了一种使用不符合 AuthGuard
的陌生界面实现 passport-facebook-token
的策略。
@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 将调用验证函数(使用 @nestjs/passport 中的 validate() 方法实现)。应该有办法在构造函数中传递一个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 的处理方式是什么,或者应该如何处理错误。
您的 Strategy
使用 extends PassportStrategy()
class 设置是正确的。为了捕获来自 passport 的错误,您可以扩展 AuthGuard('facebook')
并向 handleRequest()
添加一些自定义逻辑。您可以 read more about it here,或从文档中查看此片段:
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,但底层逻辑和处理程序是相同的,因此它应该仍然适合您。
在我的例子中,我曾经将 passport-facebook-token
与旧版本的 nest 一起使用。要升级,就需要调整策略。我对回调也不感兴趣 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
。