Setting Up Basic Authentication and Sessions on a GraphQL Server using Passport.js & Express Sessions.

A quick walkthrough on basic user authentication and persistence using Express sessions, Passport.js and GraphQL.

app
┣ src
┣ server
┃ ┣ database
┃ ┃ ┣ db.js
┃ ┃ ┣ Users.js
┃ ┃ ┣ Movies.js
┃ ┃ ┣ Favorites.js
┃ ┃ ┣ index.js
┃ ┣ gql
┃ ┃ ┣ schema.js
┃ ┣ auth
┃ ┃ ┣ google.js
┃ ┣ main.js
const express = require('express');
const session = require('express-session');
const { User, db } = require('./database/index');
const SequelizeStore = require('connect-session-sequelize')(session.Store);
const PORT = 8080;
const sessionStore = new SequelizeStore({ db });
const graphqlHTTP = require('express-graphql');
const schema = require('./gql/schema');
const { googleAuth } = require('./auth/google');
const passport = require('passport');

const app = express();

app.use(
session({
secret: 'The most robust of secrets...',
store: sessionStore,
resave: false,
saveUninitialized: false
})
);

app.use(passport.initialize());
app.use(passport.session());

passport.serializeUser((user, done) => done(null, user.id));
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findByPk(id);
done(null, user);
} catch (err) {
done(err);
}
});
app.use('/googleAuth', googleAuth)
app.use('/gqlapi', graphqlHTTP({ schema, graphiql: true }));

db.sync().then(() => {
console.log('db synced!');
app.listen(PORT, () =>
console.log(`Serving up the scenes on port ${PORT}!`)
);
});
app.use('/gqlapi', graphqlHTTP({ schema, graphiql: true }));
const passport = require('passport');
const googleAuth = require('express').Router();
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
const { User } = require('../database/index');
module.exports = googleAuth;

if (!process.env.GOOGLE_CLIENT_ID || !process.env.GOOGLE_CLIENT_SECRET) {
console.log(
'No Google Client ID or Google Client Secret found. Skipping Google OAuth.'
);
} else {
const googleConfig = {
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK
};

const strategy = new GoogleStrategy(
googleConfig,
(token, refreshToken, profile, done) => {
const googleId = profile.id;
const email = profile.emails[0].value;

User.findOrCreate({ where: { googleId }, defaults: { email } })
.then(([user]) => done(null, user))
.catch(done);
}
);

passport.use(strategy);

googleAuth.get('/', passport.authenticate('google', { scope: 'email' }));
googleAuth.get(
'/callback',
passport.authenticate('google', {
successRedirect: '/home',
failureRedirect: '/login'
})
);
}
const { Movie, User, Favorite } = require('../database/index');
const {
GraphQLObjectType,
GraphQLString,
GraphQLList,
GraphQLID,
GraphQLSchema,
GraphQLBoolean
} = require('graphql');

// Type Defs
const MovieType = new GraphQLObjectType({
name: 'Scene',
fields: () => ({
id: { type: GraphQLID },
title: { type: GraphQLString },
imdbLink: { type: GraphQLString },
})
});

const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: { type: GraphQLID },
email: { type: GraphQLString },
password: { type: GraphQLString },
favorites: {
type: new GraphQLList(FavoriteType),
async resolve(parent, args) {
const userFavorites = await Favorite.findAll({
where: {
userId: parent.id
}
});
return userFavorites;
}
}
})
});

const FavoriteType = new GraphQLObjectType({
name: 'Favorite',
fields: () => ({
id: { type: GraphQLID },
userId: { type: GraphQLID },
movieId: { type: GraphQLID },
// Resolver to get movie associated with favorite
movie: {
type: MovieType,
async resolve(parent, args) {
return await Movie.findByPk(parent.movieId);
}
}
})
});

// Root query def
const RootQuery = new GraphQLObjectType({ ... })

// Mutations def
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
loginUser: {
type: GraphQLBoolean,
args: {
email: { type: new GraphQLNonNull(GraphQLString) },
password: { type: new GraphQLNonNull(GraphQLString) }
},
async resolve(parent, args, request) {
const user = await User.findOne({ where: { email: args.email } });
if (!user) {
throw new Error(
`Could not find account associated with email: ${args.email}`
);
} else if (!user.correctPassword(args.password)) {
throw new Error(
`Incorrect password for account associated with: ${args.email}`
);
} else {
request.login(user, error => (error ? error : user));
return true;
}
}
},
addFavorite: {
type: FavoriteType,
args: {
sceneId: { type: new GraphQLNonNull(GraphQLID) }
},
async resolve(parent, args, request) {
if (!request.user) throw new Error('Only users can create favorites.');
const newFavorite = await Favorite.create({
sceneId: args.sceneId,
userId: request.user.id
});
return newFavorite;
}
}
});

module.exports = new GraphQLSchema({
query: RootQuery,
mutation: Mutation
});
async resolve(parent, args, request) {
if (!request.user) throw new Error('Only users can create favorites.');

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store