【问题标题】:How can I authenticate a GraphQL endpoint with Passport?如何使用 Passport 对 GraphQL 端点进行身份验证?
【发布时间】: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
})));

【问题讨论】:

    标签: express passport.js graphql


    【解决方案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 之外的任何东西。
    猜你喜欢
    • 2016-07-28
    • 2020-08-04
    • 1970-01-01
    • 1970-01-01
    • 2014-07-06
    • 1970-01-01
    • 2019-01-31
    • 1970-01-01
    • 2016-08-15
    相关资源
    最近更新 更多