【问题标题】:facebook-passport with NestJS使用 NestJS 的 facebook-passport
【发布时间】:2020-09-01 23:12:27
【问题描述】:

我研究了 passport-facebookpassport-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-facebookpassport-facebook-token 之一,以便它使用@nestjs/passportvalidate() 方法?来自文档:对于每个策略,Passport 将调用验证函数(使用 @nestjs/passport 中的 validate() 方法实现)。应该有办法在构造函数中传递一个clientIdclientSecret,然后把剩下的逻辑放到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 处理它的方式是什么,或者应该如何进行错误处理。

【问题讨论】:

    标签: node.js passport.js nestjs passport-facebook passport-facebook-token


    【解决方案1】:

    就我而言,我曾经将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

    【讨论】:

      【解决方案2】:

      Strategy 使用 extends PassportStrategy() 类设置,你走在正确的轨道上。为了从护照中捕获错误,您可以扩展AuthGuard('facebook') 并向handleRequest() 添加一些自定义逻辑。您可以read more about it here,或查看文档中的这个 sn-p:

      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,但底层逻辑和处理程序是相同的,因此它应该仍然适合您。

      【讨论】:

      • 非常感谢 Jay,我不知道我是如何忽略这一点的。这正是我所需要的。
      • 你知道err是什么类型的对象吗?它似乎是一个难以解析的字符串
      • 不。问题之一是护照不是一个打字包,所以谁知道它返回了什么错误。
      • 这个答案也为我节省了很多时间。也许他们应该在文档中写得更好,我也喜欢@randombits 就像'我怎么忽略了这个!'
      • 你会如何建议它写得更好? It's already linkable,这意味着它在页面上有一个标题。通过创建一个Strategy 类并使用内置的AuthGuard(),每个护照策略都可以用于Nest。
      猜你喜欢
      • 2021-01-13
      • 2020-07-18
      • 2021-05-28
      • 2022-11-02
      • 2020-03-24
      • 2013-12-12
      • 2019-04-15
      • 1970-01-01
      • 2013-12-01
      相关资源
      最近更新 更多