如何使用 Passport 对 GraphQL 端点进行身份验证?

Posted

技术标签:

【中文标题】如何使用 Passport 对 GraphQL 端点进行身份验证?【英文标题】:How can I authenticate a GraphQL endpoint with Passport? 【发布时间】:2018-06-02 15:28:36 【问题描述】:

我有一个 GraphQL 端点:

app.use('/graphql', graphqlHTTP(request => (
  graphiql: true,
  schema
)));

我还有一个用于登录的 Passport 路由(并处理回调,因为我使用的是 Google OAuth2):

this.app.get('/login', passport.authenticate('google'));
this.app.get('/auth/callback/google', ....

Passport 将用户添加到请求中,我可以在网上找到的所有文章都建议使用该方法在我的每个 GraphQL 解析器中进行身份验证:

resolve: (root, args,  user ) => 
  if (!user) throw new NotLoggedInError();

但是,当它应用于所有解析器时,必须将该逻辑添加到每个解析器是没有意义的,所以我希望以某种方式对整个端点进行身份验证。

问题是我不确定如何组合中间件。我尝试了以下方法,但它只是破坏了端点:

app.use('/graphql',  passport.authenticate('google'), graphqlHTTP(request => (
  graphiql: true,
  schema
)));

【问题讨论】:

【参考方案1】:

我有以下工作。我遇到的一些问题是确保启用了我的 google API 并启用了正确的范围。我也只在身份验证端点上使用护照中间件,并使用isAuthenticated 中间件来检查会话是否经过身份验证,如果没有重定向到身份验证端点。还将请求对象放入上下文中,以便解析器可以使用它来潜在地授权用户。您当然需要更新用户查找,因为我只是在传递模拟数据。

import express from "express";
import graphqlHTTP from "express-graphql";
import passport from "passport";
import cookieParser from "cookie-parser";
import session from "express-session";
import  Strategy as GoogleStrategy  from "passport-google-oauth20";
import  buildSchema  from "graphql";

const PORT = 5000;

const data = [
   id: "1", name: "foo1" ,
   id: "2", name: "foo2" ,
   id: "3", name: "foo3" ,
];

const def = `
type Foo 
  id: String!
  name: String

type Query 
  readFoo(id: String!): Foo

schema 
  query: Query

`;
const schema = buildSchema(def);
const fieldMap = schema.getType("Query").getFields();
fieldMap.readFoo.resolve = (source, args) => 
  return data.filter(( id ) => id === args.id)[0] || null;
;

passport.serializeUser((user, done) => 
  done(null, user);
);

passport.deserializeUser((obj, done) => 
  done(null, obj);
);

passport.use(
  new GoogleStrategy(
    
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: `http://localhost:$PORT/auth/google/callback`,
    ,
    (accessToken, refreshToken, profile, cb) => 
      return cb(null, 
        id: "1",
        username: "foo@bar.baz",
        googleId: profile.id,
      );
    
  )
);

function isAuthenticated(req, res, next) 
  return req.isAuthenticated() ? next() : res.redirect("/auth/google");


const app = express();
app.use(cookieParser());
app.use(
  session(
    secret: "sauce",
    resave: false,
    saveUninitialized: false,
  )
);
app.use(passport.initialize());
app.use(passport.session());

app.get("/auth/fail", (req, res) => 
  res.json( loginFailed: true );
);

app.get(
  "/auth/google",
  passport.authenticate("google",  scope: ["profile"] )
);

app.get(
  "/auth/google/callback",
  passport.authenticate("google",  failureRedirect: "/auth/fail" ),
  (req, res) => 
    res.redirect("/graphql");
  
);

app.use(
  "/graphql",
  isAuthenticated,
  graphqlHTTP((req) => (
    schema,
    graphiql: true,
    context: req,
  ))
);

app.listen(PORT, () => 
  console.log("Started local graphql server on port ", PORT);
);


【讨论】:

对于未来的读者,那里有很多代码,但答案的关键是isAuthenticated 函数(以及它在app.use('/graphql', ... 调用中的使用方式)。【参考方案2】:

vbranden 的回答非常好,这是此回答的基础。但是,他的答案有很多其他代码使解决方案有些模糊。我不想弄乱它,因为它提供了更完整的事物视图,但希望这个答案会更直接地以自己的方式有所帮助。但同样,这个解决方案的所有功劳都属于 vbranden(请相应地支持他的答案)。

如果您使用适当的签名 (request, response, next) 创建了 isAuthenticated 函数,那么您可以在设置 GraphQL 端点时“链接”该函数:

function isAuthenticated(req, res, next) 
  return req.isAuthenticated() ?
    next() :
    res.redirect('/auth/google');


app.use(
  '/graphql',
  isAuthenticated,
  graphqlHTTP(req => (
    schema,
    graphiql: true,
    context: req
  ))
);

【讨论】:

这不会使所有 graphql 端点都受到保护吗?如果我们只想保护选定的路由怎么办? 取决于您要完成的工作。我希望我的所有 api 都只能被登录用户访问,所以这种方法对我来说很有意义。 我希望在 graphql 端点内保护访客路由(登录、忘记密码、注册)。所以我可以将单个 api 端点与 apollo 客户端一起使用。 你为什么要保护那些?您想让您的用户登录以进入登录页面吗? 我的意思是我希望 graphql 同时拥有受保护和不受保护的路由。我可以将一个 api 端点附加到 apollo 客户端。如果我将登录路线与 graphql 分开,我需要使用 axios 或 fetch 之类的东西来登录……然后使用 gql 来查询其余数据。我不想在前端使用除了 graphql 之外的任何东西。

以上是关于如何使用 Passport 对 GraphQL 端点进行身份验证?的主要内容,如果未能解决你的问题,请参考以下文章

Passport (oAuth2) 如何与 GraphQL (TypeGraphQL) 一起使用?

使用 express js、passport s 保护 GraphQL 查询

带有 Passport JWT 的 Apollo 服务器

NestJS:使用 graphql 的 Passport LinkedIn 策略

如何在我的 graphQL 上下文中反序列化用户

GraphQL 中的社交身份验证