GraphQl和passport会话:在查询GraphQl时访问req.user

时间:2023-01-19 23:54:44

I have a GraphQl server and a react frontend. I use passport and LocalStrategy to authenticate the user which works well, I can successfully login an existing user. I also want to use passport session to create a user session, so that I can access the logged in user later in my GraphQl resolvers for authentication. I expected passport to set the user in the session after successfully authenticating one. But after sending correct credentials from the client to the server, GraphQl queries do not have access to req.user.

我有一个GraphQl服务器和一个反应前端。我使用passport和LocalStrategy来验证运行良好的用户,我可以成功登录现有用户。我还想使用护照会话来创建用户会话,以便稍后我可以在我的GraphQl解析器中访问登录用户进行身份验证。我预计护照会在成功验证用户后在会话中设置用户。但是,在从客户端向服务器发送正确的凭据后,GraphQl查询无法访问req.user。

The GraphQL server code looks like this:

GraphQL服务器代码如下所示:

import express from 'express';
import passport from 'passport';
import {Strategy as LocalStrategy} from 'passport-local';
import session from 'express-session';
import cors from 'cors';
import bodyParser from 'body-parser';
import models from './models';
import typeDefs from './schema';
import resolvers from './resolvers';
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express';
import { makeExecutableSchema } from 'graphql-tools';

export const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
});

const app = express();

app.use('*', cors({ origin: 'http://localhost:3000' }));

app.set('port', (process.env.PORT || 3001));

//--- Passport ----
app.use(session({ 
  saveUninitialized: true, 
  resave: false,
  secret: 'verysecretsecret'
}));
app.use(passport.initialize());
app.use(passport.session());

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

passport.deserializeUser((user, done) => {
  done(null, user);
});

passport.use(new LocalStrategy(
  {
    usernameField: 'email',
    passwordField: 'password',
  },
  function(email, password, done) {
    models.User.findOne({
      where: {
          email: email
      }
    }).then(function(user) {
      if (user) {
        if (user.validPassword(password)) {
          return done(null, user);
        } else {
          return done(null, false);
        }
      } 
      return done(null, false);     
    });    
  }
));

//--- Routes ----
app.use('/graphiql', graphiqlExpress({ 
    endpointURL: '/graphql' 
}));

app.use(
  '/graphql',
  bodyParser.json(),
  graphqlExpress( (req) => {
    console.log('/graphql User: ' + req.user); // prints undefined after sending correct login credentials to /login
    return ({
    schema,
    context: {
      user: req.user,
    },
  });}),
);

app.use(bodyParser.urlencoded({ extended: true }) );
app.post('/login', passport.authenticate('local'), (req, res) => {
  console.log('/login: User', req.user); // prints the logged in user's data
  return res.sendStatus(200);
});

export default app;

And this is the login fetch request from the client:

这是来自客户端的登录提取请求:

onSubmit = () => {

    var details = {
      'email': this.state.email,
      'password': this.state.password,
    };

    var formBody = [];
    for (var property in details) {
      var encodedKey = encodeURIComponent(property);
      var encodedValue = encodeURIComponent(details[property]);
      formBody.push(encodedKey + "=" + encodedValue);
    }
    formBody = formBody.join("&");

    fetch('http://localhost:3001/login', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
      },
      credentials: 'include',
      body: formBody
    }).then(function(response) {
      console.log(response);
    }).catch(function(err) {
      // Error
    });
  };

Do I have to change something on the client side for the server to receive the session cookie? Or is something going wrong in the backend?

我是否必须在客户端更改某些内容以使服务器接收会话cookie?或者后端出了什么问题?

I also uploaded a minimal example to this repo: https://github.com/schmitzl/passport-graphql-minimal-example

我还向此repo上传了一个最小的示例:https://github.com/schmitzl/passport-graphql-minimal-example

1 个解决方案

#1


4  

Managing sessions gets a little messy when you're dealing with CORS. There's a couple of things you need to change to get the behavior you're expecting:

当你处理CORS时,管理会话会有点混乱。您需要更改几件事来获得您期望的行为:

First, modify your server code to ensure you're sending the Access-Control-Allow-Credentials header:

首先,修改服务器代码以确保您发送Access-Control-Allow-Credentials标头:

app.use('*', cors({ origin: 'http://localhost:3000', credentials: true }));

Next, make sure your requests are actually including the cookies. You've done so with the login request, by setting the credentials option to include. Apollo uses fetch under the hood and we need to pass this option to it as well.

接下来,确保您的请求实际包含Cookie。您已通过将凭据选项设置为include来完成登录请求。 Apollo在引擎盖下使用fetch,我们也需要将此选项传递给它。

I may be missing something, but it doesn't appear that apollo-boost provides an easy way to do the above (you have fetchOptions, but including credentials there doesn't appear to do anything). My advise would be to scrap apollo-boost and just use the appropriate libraries directly (or use apollo-client-preset). Then you can pass the appropriate credentials option to HttpLink:

我可能会遗漏一些东西,但似乎没有apollo-boost提供了一种简单的方法来执行上述操作(您有fetchOptions,但包括凭据似乎没有做任何事情)。我的建议是废弃apollo-boost并直接使用适当的库(或使用apollo-client-preset)。然后,您可以将适当的凭据选项传递给HttpLink:

import ApolloClient from 'apollo-client'
import { HttpLink, InMemoryCache } from 'apollo-client-preset'

const client = new ApolloClient({
  link: new HttpLink({ uri: apolloUri, credentials: 'include' }),
  cache: new InMemoryCache()
})

#1


4  

Managing sessions gets a little messy when you're dealing with CORS. There's a couple of things you need to change to get the behavior you're expecting:

当你处理CORS时,管理会话会有点混乱。您需要更改几件事来获得您期望的行为:

First, modify your server code to ensure you're sending the Access-Control-Allow-Credentials header:

首先,修改服务器代码以确保您发送Access-Control-Allow-Credentials标头:

app.use('*', cors({ origin: 'http://localhost:3000', credentials: true }));

Next, make sure your requests are actually including the cookies. You've done so with the login request, by setting the credentials option to include. Apollo uses fetch under the hood and we need to pass this option to it as well.

接下来,确保您的请求实际包含Cookie。您已通过将凭据选项设置为include来完成登录请求。 Apollo在引擎盖下使用fetch,我们也需要将此选项传递给它。

I may be missing something, but it doesn't appear that apollo-boost provides an easy way to do the above (you have fetchOptions, but including credentials there doesn't appear to do anything). My advise would be to scrap apollo-boost and just use the appropriate libraries directly (or use apollo-client-preset). Then you can pass the appropriate credentials option to HttpLink:

我可能会遗漏一些东西,但似乎没有apollo-boost提供了一种简单的方法来执行上述操作(您有fetchOptions,但包括凭据似乎没有做任何事情)。我的建议是废弃apollo-boost并直接使用适当的库(或使用apollo-client-preset)。然后,您可以将适当的凭据选项传递给HttpLink:

import ApolloClient from 'apollo-client'
import { HttpLink, InMemoryCache } from 'apollo-client-preset'

const client = new ApolloClient({
  link: new HttpLink({ uri: apolloUri, credentials: 'include' }),
  cache: new InMemoryCache()
})