Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GraphQl and passport session: access req.user when querying GraphQl

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

like image 727
Lisa Schmitz Avatar asked Feb 28 '18 19:02

Lisa Schmitz


People also ask

How do I get the current user in GraphQL?

Using the Parse GraphQL API, you can get a logged user's data just by sending the user's sessionToken through the X-Parse-Session-Token header (as described in the authenticating a user recipe) and calling the me query.

What does a GraphQL client usually do before caching the results of a query?

Generally, when caching data, the intuition is to put information that's fetched remotely into a local store from where it can be retrieved later on. With GraphQL, the naive approach would be to simply put the results of GraphQL queries into the store and simply return them whenever the same query is sent.

How do you do authentication in GraphQL?

This mutation comes from the client-side, then the GraphQL login resolver will be called on the server to handle the login. On the server, the register resolver function will handle this. It will set up the user in the database using the credentials passed in. So, this is how authentication is done in GraphQL.

Is GraphQL server side or client side?

A GraphQL server is a server-side implementation of the GraphQL spec. In other words, a GraphQL server exposes your data as a GraphQL API that your client applications can query for data. These clients could be a single page application, a CMS like Drupal, a mobile app, or almost anything.


1 Answers

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()
})
like image 161
Daniel Rearden Avatar answered Oct 07 '22 13:10

Daniel Rearden