0
votes

The Problem

I am setting up my NodeJS & Express App, using Passport for authentication with Google Sign In and Facebook Login. Everything works very well when working on localhost, but when I deploy my app (to Vercel) I can't make the sessions work.

The signin process works very well, and I can see the user information attached to req.session, and req.user contains all user information.

However, right after that, when calling any route, req.user is undefined

The code

//Setting up the app
const express = require('express');
//const https = require('https');

//Bodyparser to handle JSON
const bodyParser = require('body-parser');

//Mongoose to connect to MongoDB
const mongoose = require('mongoose');

//To handle environments
const keys = require('./keys')

//To handle user sessions
var session = require("express-session");
var MongoDBStore = require('connect-mongodb-session')(session);


//Passport to hangle Google Sign In
var passport = require('passport');
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;

//Authentication: serialize and deserialize user
passport.serializeUser(function(user, done) {
  console.log(`serialize: user:${user}`); 
  done(null, user._id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    console.log(`deserialize user:${user}`)
    done(err, user);
  });
});

//Authentication: Google Sign In
passport.use(new GoogleStrategy({
    clientID: keys.GOOGLE_CLIENTID,
    clientSecret: keys.GOOGLE_SECRET,
    callbackURL: keys.URL_GOOGLE_CALLBACK
  },
  function callback(accessToken, refreshToken, profile, done) {
    process.nextTick( ()=> {
        User.findOne({'googleId': profile.id}, (err,user)=> {
            if(err)
                return done(err,false);
            if(user){
                return done(null, user);
            }
            else{
                var newUser = new User();
                newUser.googleId = profile.id;
                newUser.googleToken = accessToken;
                newUser.firstName  = profile.name.givenName;
                newUser.email = profile.emails[0].value;
                newUser.authType = "Google";
                newUser.image = profile.photos[0].value;

                newUser.save((err)=> {
                    if(err)
                        throw err;
                    return done(null, newUser);
                });
            }
        });
      });
  }
));

//Importing the models used
const Country = require('./models/country');
const Place = require('./models/place');
const User = require('./models/user');
const Trip = require('./models/trip');

//Starting the app
const app = express();

//Using body parser as global middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }))

//Using cookie bodyParser
app.use(require('cookie-parser')('keyboard cat'))

//Using Cors for routes
//app.use(cors())

//Store for sessions
var storeSessions = new MongoDBStore({
  uri: keys.URI_MONGO,
  collection: 'sessions'
});

// Catch errors
storeSessions.on('error', function(error) {
  console.log(error);
});

//Added but not sure if useful
app.use(cors({credentials: true, origin: true}));


app.set('trust proxy', 1);

//Setting up a session
app.use(session({
  secret: 'keyboard cat',
  resave: true,
  saveUninitialized: false,
  proxy: true,
  store: storeSessions,
  cookie : {
    maxAge: 2419200000
  }
}));

//Using passport for authentication
app.use(passport.initialize());
app.use(passport.session());

//Solving CORS issues

app.use((req, res, next) => {
  let allowedOrigins = ['http://localhost:3000', 'http://localhost:8080', 'mywebsite.com'];
  let origin = req.headers.origin;
  if(allowedOrigins.indexOf(origin) > -1){
       res.setHeader('Access-Control-Allow-Origin', origin);
  }
  //res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content, Accept, Content-Type, Authorization');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
  res.header('Access-Control-Allow-Credentials', true);
  next();
});



//Connection to MongoDB
mongoose.connect(keys.URI_MONGO, {useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('Connection successful!'))
  .catch((error) => { console.error(error); });



//The API call from the Google sign in button
app.get('/authgoogle',
  passport.authenticate('google', { scope : ['profile', 'email'] }));

//Callback from Google Sign in
app.get('/authgoogleredirect', function(req, res, next) {
  passport.authenticate('google', {
      scope : ['profile', 'email']
    }, function(err, user, info) {
    if (err) { return next(err); }
    if (!user) { return res.redirect(keys.URL_SIGN_IN); }
    req.logIn(user, function(err) {
      if (err) { return next(err); }
      req.session.user = req.user;
      req.session.save(function(){
        console.log(`req.session.user in callback: ${req.session.user}`)
        console.log(`req.session in callback: ${JSON.stringify(req.session)}`)
        res.redirect(keys.URL_LOGGED_IN);
      });
    });
  })(req, res, next);
});



//Middleware to check if user is logged in
function isLoggedIn(req, res, next) {
  console.log(`req.session in isloggedin: ${JSON.stringify(req.session)}`);
  console.log(`req.user in isloggedin: ${JSON.stringify(req.user)}`);
  if (req.isAuthenticated()) {
    next();
  } else {
    data = {
      "redirect": true,
      "url": keys.URL_SIGN_IN
    }
    res.send(JSON.stringify(data));
  }
}


//--------------------USERS------------------------
//Getting the currently logged in user
app.get('/getcurrentuser', isLoggedIn, function(req, res, next) {
  res.send(req.user);
});

What works and what does not

The google sign in works well, and passport.serialize and passport.deserialize work well, I can see the user object.

When I try to reach GET /getcurrentuser (call on all pages), I don't get the req.user object and isAuthenticated() always returns false.

These are the logs of the middleware isloggedin : req.session in isloggedin: {"cookie":{"originalMaxAge":2419200000,"expires":"2020-08-07T12:09:54.220Z","httpOnly":true,"path":"/"}} req.user in isloggedin: undefined

The sessions are correctly saved in the session collection in mongoDB.

Serialize and deserialize don't seem to be called after sign in, not sure if that's normal.

What I tried

I read plenty of answers to similar problems but none seem to work:

  • order of initialization of passport and express session: it should be correct
  • proxy: added but did not change anything
  • credentials: include has always been included in my front end fetch call
  • cookie.secure: false: turned it on and off, without success
  • cookie session tried to use it instead of express session, without success

It must be something stupid but I can't seem to find it... What troubles me is why would this work on localhost?

Thanks a lot for your time :)

2
Edited my initial comment with new problemflob

2 Answers

0
votes

Passport has its own session handling code embedded in its library, if you want to store your passport login user to session-database you can do

router.get("/redirect", passport.authenticate('google',{failureRedirect:'/fail',successRedirect:'/success'}, (req,res)=>{
   req.session.user = req.user;
});

and keeping your remaining code same as you have done, this will save your session in the database

0
votes

Alright, if anyone comes across this exact issue, I finally solved it:

req.user always came as undefined, because my front end and back end have different domain names (I'm using the free heroku tier) therefore the cookie was considered third-party, blocked by Google Chrome and never sent to my express app!

As soon as I disabled third-party cookie blocking in Chrome settings the session became persistent!

The passport and session configurations were correct all along...

Thanks @abhinav for your help and time :)