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
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.
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.
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.
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.
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()
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With