8

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.

The GraphQL server code looks like this:

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?

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

1 Answers1

8

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:

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

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.

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:

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()
})
Daniel Rearden
  • 58,313
  • 8
  • 105
  • 113
  • Thank you, that solved the problem! The cookie is now correctly set in the graphql query and I can access the user object. – Lisa Schmitz Mar 01 '18 at 09:15