如何使用Passport验证GraphQL端点?

时间:2017-12-20 06:12:13

标签: express passport.js graphql

我有一个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
})));

2 个答案:

答案 0 :(得分:4)

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

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);
});

答案 1 :(得分:0)

vbranden的答案非常好,这是这个答案的基础。但是,他的答案还有很多其他代码可以使解决方案混淆不清。我不想搞砸它,因为它提供了更完整的事物视图,但希望这个答案通过更直接的方式以自己的方式有所帮助。但同样,这个解决方案的所有功劳都属于vbranden(请相应地提出他的回答)。

如果使用适当的签名(isAuthenticated)创建request, response, next函数,则可以在设置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
  }))
);