Apollo-server: User is undefined in context after authenticate with Passport

Created on 5 Aug 2018  路  12Comments  路  Source: apollographql/apollo-server

I'm trying to access user in Apollo Server 2 context but got undefined. The req.user is correctly defined in the express middleware.

You can find a minimal reproduction here

server.js

import { ApolloServer, gql } from 'apollo-server-express'

import { typeDefs, resolvers } from './api/schema'
import express from 'express'
import session from 'express-session'
import bodyParser from "body-parser"
import passport from 'passport'
import './api/passport'

const app = new express()

app.use(express.static("public"));
app.use(session({
    secret: "cats",
    resave: true,
    saveUninitialized: false
}));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(passport.initialize());
app.use(passport.session());

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
        // req.user is undefined
    console.log("req:", req.user)
  }
});

app.post('/login',
  passport.authenticate('local', { session: true }),
  function(req, res) {
        // req.user defined
    console.log('in login:', req.user)
    res.redirect('/ok');
    }
);

app.get('/ok',
  function(req, res) {
        // req.user defined
    console.log('in ok:', req.user)
    res.json(req.user)
    }
);

server.applyMiddleware({ app });

app.listen({ port: 3000 }, () =>
  console.log(`馃殌 Server ready at http://localhost:3000${server.graphqlPath}`)
)

passport.js

import passport from 'passport'
import { Strategy as LocalStrategy } from 'passport-local'
import User from './db'

passport.serializeUser(function(user, done) {
  console.log("=============================SerializeUser called")
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  console.log("=============================DeserializeUser called on: ", id)
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

passport.use('local', new LocalStrategy(
  function(username, password, done) {
    User.findOne({ username: username }, function (err, user) {
      if (err) { return done(err); }
      if (!user) {
        return done(null, false, { message: 'Incorrect username.' });
      }
      if (!user.validPassword(password)) {
        return done(null, false, { message: 'Incorrect password.' });
      }
      return done(null, user);
    });
  }
));
has-reproduction

Most helpful comment

If you testing on playground, u need aded playground: { settings: { 'request.credentials': 'include', }, }, in ApolloServer options. Example use passport-local & graphQL you can see here https://github.com/bakhaa/react-express/blob/master/api/resolvers/user.js

All 12 comments

It looks like you are not calling the passport.authenticate() middleware in your /graphql route

Could you show me where to add the middleware ? Do I need to modify the applyMiddleware function ?

You just need to call passport middleware before server.applyMiddleware.

app.post('/login',
  passport.authenticate('local', { session: true }),
  function(req, res) {
        // req.user defined
    console.log('in login:', req.user)
    res.redirect('/ok');
    }
);

app.get('/ok',
  function(req, res) {
        // req.user defined
    console.log('in ok:', req.user)
    res.json(req.user)
    }
);
// default endpoint path is /graphql 
app.use('/graphql',  passport.authenticate('local', { session: true }));

server.applyMiddleware({ app });

app.listen({ port: 3000 }, () =>
  console.log(`馃殌 Server ready at http://localhost:3000${server.graphqlPath}`)
)

It looks like the graphql server is not running anymore when I add the middleware. I got a 400 error code.

@HBCharles did you solve this?

Faced similar problem, but in the end the issue was my lack of understand on how passportjs lib works.
Had to customize passport.authenticate callback function to bypass the authentication error and manually set the req.user value

I don't understand the significance of this line:

app.use('/graphql',  passport.authenticate('local', { session: true }));

No examples I have seen explicitly add any passport auth middlewares to the /graphql endpoint. Also what if you have many passport providers, like Steam and Google? Do you need to add all of them as middleware to the /graphql endpoint?

This might be what is causing the issue I just posted (#1657), but I'm confused because I have never seen examples (neither official nor unofficial) that explicitly mount middleware over gql.

Furthermore, if I add a middleware on /graphql like you suggest @tux-tn, it sends me to auth with the provider (Steam in the case I tested), which sends me back to my loginUrl, and then when I go back to /graphql it does the same thing. Over and over. So I don't see how mounting middleware like that is supposed to help.

If you testing on playground, u need aded playground: { settings: { 'request.credentials': 'include', }, }, in ApolloServer options. Example use passport-local & graphQL you can see here https://github.com/bakhaa/react-express/blob/master/api/resolvers/user.js

On the extension from @bakhaa 's solution, if you are using apollo-link-http in frontend, you should add credentials option with 'include' to attach your session cookie on your request to GraphQL server, otherwise there will be nothing with passport on req.session, including req.user.

const httpLink = createHttpLink({
  uri: `${API_URL}/graphql`,
  credentials: 'include'
})

please improve documentation for passport / oauth / session with context in apollo server

I am having the same issue...

This is my GraphQL local strategy

passport.use(
    new GraphQLLocalStrategy((email, password, next) => {
        console.log(`馃帿  ${JSON.stringify(User)} 馃殧  馃懏鈥嶁檪`)
        User.findOne({ email })
            .then(user => !user
                ? next(null, false, 'Invalid email or password')
                : user.checkPassword(password) //bcrypt
                        .then(match => !match
                            ? next(null, false, 'Invalid email or password')
                            : next(null, user)
                        )
            )
            .catch(error => next(error))
    }),
);

and the apolloServer configuration:

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req, res }) => buildContext({ req, res, User }),
  playground: {
    settings: {
      'request.credentials': 'same-origin',
    },
  },
})

Where do I have to apply the passport.authenticate(?) @tux-tn
Did you find a solution @HBCharles

Was this page helpful?
0 / 5 - 0 ratings