Express-mysql-session preventing passport deserializeUser from running

JavascriptMysqlnode.jsExpresspassport.js

Javascript Problem Overview


I have an app using passport.js to log in users through facebook, and am attempting to use express-mysql-session to persist their login states. If I don't include the express-mysql-session code, the passport serializeUser and deserializeUser functions hit fine...however when I un-comment the code that attempts to store their session with express-mysql-session, the deserializeUser function doesn't get hit, and the user never gets properly logged in.

server.js file

var express      = require('express');
var mysql		 = require('mysql');
var passport     = require('passport');
var session      = require('express-session');
var MySQLStore   = require('express-mysql-session')(session);

if (typeof process.env.OPENSHIFT_MYSQL_DB_HOST === "undefined"){
	var options = {
		host     : 'localhost',
		port     : '3307',
		user     : 'user',
		password : 'password',
		database : 'database',
		socketpath: '/var/run/mysqld/mysqld.sock'
	}
} else { 
	var options = {
		host     : process.env.OPENSHIFT_MYSQL_DB_HOST,
		port     : process.env.OPENSHIFT_MYSQL_DB_PORT,
		user     : process.env.OPENSHIFT_MYSQL_DB_USERNAME,
		password : process.env.OPENSHIFT_MYSQL_DB_PASSWORD,
		database : process.env.OPENSHIFT_APP_NAME,
		socket	 : process.env.OPENSHIFT_MYSQL_DB_SOCKET
	}
};	  

var connection = mysql.createConnection(options);

var sessionStore = new MySQLStore({
    checkExpirationInterval: 900000,// How frequently expired sessions will be cleared; milliseconds.
    expiration: 86400000,// The maximum age of a valid session; milliseconds.
    createDatabaseTable: false,// Whether or not to create the sessions database table, if one does not already exist.
	connectionLimit: 1,
    schema: {
        tableName: 'LoginRequests',
        columnNames: {
            session_id: 'loginID',
            expires: 'expires',
			data:'data'
        }
    }
}, connection);

 self.initializeServer = function() {
        self.app = module.exports = express();
        self.app.configure(function() {
			self.app.set('views', __dirname + '/public');
            self.app.set('view engine', 'html');
			self.app.engine('html', require('hogan-express'));
			self.app.enable('view cache');
            self.app.use(express.favicon());
            self.app.use(express.logger('dev'));
            self.app.use(express.bodyParser());
            self.app.use(express.methodOverride());
            self.app.use(express.cookieParser('secret'));
			self.app.use(session({
			    key: 'session_cookie_name',
			    secret: 'secret',
			    cookie: {maxAge: 3600000, secure:false},
			    store: sessionStore,
			    resave: false,
			    saveUninitialized: false
			}));
			// required for passport
			self.app.use(passport.initialize());
			self.app.use(passport.session()); // persistent login sessions
            self.app.use(express.static(path.join(__dirname, 'public')));
            self.app.use('/public',express.static(__dirname, '/public'));
            self.app.use(self.app.router);
            //self.app.use(require('stylus').middleware(__dirname + '/public'));

			
		});
        
    require('./routes/site.js');  
	require('./config/passport.js')(passport); // pass passport for configuration 
	 
    }

So, if I comment out the "store" option in the session object above, the passport functions get hit. If I leave this line un-commented, the deserializeUser function does not get hit.

Passport functions

passport.serializeUser(function(user, done) {
		console.log('you have been serialized!');
			done(null, user.id);
	});
 	
	
	// used to deserialize the user
	passport.deserializeUser(function(id, done) {
		console.log('you have been deserialized!');
		connection.query("SELECT * FROM Users WHERE id = "+id,function(err,rows){
			done(err, rows[0]);
		});
	});

EDIT

Mor Paz suggested that I include some of the logs from when I run my server with the debug module. Below are the logs right before, and immediately after the user is serialized. The user should be deserialized at some point near this, but never is.

GET /auth/facebook 302 81ms - 412b
express-mysql-session:log Getting session: oNcJ4UapxCY_zKOyfSBTUWaVhaNZuFRq +356ms
you are a user!
you have been serialized!
  express-mysql-session:log Setting session: tgRPY-Mb1VDP2zaSMOFhlf_IWFhVpTia +798ms
  express-mysql-session:log Getting session: tgRPY-Mb1VDP2zaSMOFhlf_IWFhVpTia +6ms
GET /auth/facebook/callback?    code=AQCWPvA5ZRMYoTueW6_wWU49Up5ggjW68ufOtiYkU5IzhRjSNyyWnzlQVprgQo_uubQkEVvNI0yo53ET3cWBnDAHUGmAXPBy_ITEmC-biE2KEGEr0iCm_cqjuG90nnePY-k9U2oFUbX2kvLgMeM0kZ-094EHiU_NJjmAJNj6mzTkSE47935RhJy0Tba_sYS88_C0N3kn5f5kcoTC4KsgW1gBHWWJAwZ68Lj94ffVe2hN97580CtzEpJa0wwQHwTBYfmjQ0NfUdx07m4rXW9R7PR06aHDcUDrYqR9Kb0LWq4sZLbQjV5rI7gzkWG-huhq7IY 302 825ms - 72b
  express-mysql-session:log Setting session: Xo9OjfmJzTFp1CSF6srLi_UyxTCLg-EI +56ms
  express-mysql-session:log Getting session: Xo9OjfmJzTFp1CSF6srLi_UyxTCLg-EI +23ms
  express-mysql-session:log Getting session: Xo9OjfmJzTFp1CSF6srLi_UyxTCLg-EI +2ms
GET /profile 200 84ms - 4.22kb

Javascript Solutions


Solution 1 - Javascript

It was impossible to replicate the problem, so I prepared a working example. [Github repo.]

Its crafted for Openshift, since I saw the usage of its environment variables (it can be adapted with ease for other use cases).

I did some modifications to the original concept :

  1. Replaced the old, deprecated (express) bundled middleware usages.
  2. Using a Class instead of the self = this concept
  3. Using Github instead of Facebook for the user login...
  4. Included some basic functions to include new users to the db
  5. Missing some original modules (can be included with ease)

I hope it can be useful as a starting point.

 // .: DB Configuration :.
const mysql = require('mysql'); 
var dbconf = {host:process.env.OPENSHIFT_MYSQL_DB_HOST,port:process.env.OPENSHIFT_MYSQL_DB_PORT,user:process.env.OPENSHIFT_MYSQL_DB_USERNAME,password:process.env.OPENSHIFT_MYSQL_DB_PASSWORD,database:process.env.OPENSHIFT_APP_NAME,socket:process.env.OPENSHIFT_MYSQL_DB_SOCKET}}    
const dbconn = mysql.createConnection(dbconf); /*or create a pool*/ dbconn.connect();
// .: Express & Other Middleware Modules :.
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var cookieParser = require('cookie-parser');
var serveStatic = require('serve-static');
// .: Sessions :.
var passport = require('passport');
var GitHubStrategy = require('passport-github2');
var session = require('express-session');
var MySQLStore = require('express-mysql-session')(session);
var sessionStoreConf = {
  connectionLimit:1,checkExpirationInterval:900000,expiration:86400000,
  createDatabaseTable:true,schema:{tableName:'LoginRequests',columnNames:{session_id:'loginID',expires:'expires',data:'data'}}
};
var sessionStore = new MySQLStore(sessionStoreConf,dbconn);
// .: Server (class) :.
class Server {
  constructor(port, ip){
    this.app = express();
    this.app.use(cookieParser('secret'));
    this.app.use(session({
      key:'session_cookie_name',
      secret:'secret',
      cookie:{maxAge:3600000,secure:false},
      store: sessionStore,
      resave:false,
      saveUninitialized:false
    }));
    this.app.use(passport.initialize());
    this.app.use(passport.session());
    this.app.use(serveStatic(path.join(__dirname,'public')))
    this.app.listen(port,ip,function(){console.log('[i] Application worker started.');});
    //require('./routes/site.js'); //~Example (routes/site.js) :
      this.app.get("/",function(req,res){res.send("<a href='./auth/github'>Click here to login (GitHub)</a>");})
      this.app.get('/auth/github',passport.authenticate('github',{scope:['user:email']}));
      this.app.get('/auth/github/callback',passport.authenticate('github',{failureRedirect:'/'}),function(req,res){res.redirect('/success');});
      // route for valid logins
      this.app.get('/success', function(req, res){ 
        if(req.user){ console.log(req.user);  res.send(req.user); }
        else{ res.redirect('/login'); }
      });
      // route to check the sessionStore table entries in the browser
      this.app.get('/sessions',function(req,res){
        dbconn.query("SELECT * FROM LoginRequests",function(err,rows){
          if(err){console.log(err);}else{
            if(rows.length!=0){
              res.send(JSON.stringify(rows));
              console.log(rows);
            }else{res.send("No LoginRequests found");}
          }
        });
      });
    //require('./config/passport.js')(passport);  //~Example (config/passport.js) :
      passport.use(new GitHubStrategy(
        {clientID:"clientID",clientSecret:"clientSecret",callbackURL:"callbackURL"},
        function(token, tokenSecret, user, cb){CheckUser('github',user,cb);}
      ));
    }
}
const server = new Server(process.env.OPENSHIFT_NODEJS_PORT,process.env.OPENSHIFT_NODEJS_IP);
// .: Passport : Serialize & Deserialize User :.
passport.serializeUser(function(user, done){
 console.log('[passport] serializeUser');
 done(null,user.id);
});
passport.deserializeUser(function(id, done) {
 console.log('[passport] deserializeUser');
  dbconn.query("SELECT * FROM Users WHERE id=?",[id],function(err,rows){
  if(err){console.log(err);}else{
    if(rows.length!=0){ done(err,rows[0]); }
    else{ done(err,null); }
  }
 });
});

//:Check if user exists:
function CheckUser(platform,user,cb){
  dbconn.query("SELECT * FROM Users WHERE id=?",[user.id],function(err,rows){
  if(err){console.log(err); cb(err,null);}else{
    if(rows.length!=0){cb(null,user);}
    else{CreateUser(platform,user,cb);}
    }
  });
}
  //:Create new user:
function CreateUser(platform,user,cb){
  switch(platform){
    case "github": 
      var newUserObj  = {id:user.id,platform:platform,email:user.emails[0].value};
      dbconn.query("INSERT INTO Users SET ?",newUserObj,function(err){
        if(err){console.log(err); cb(err,null);}else{cb(null,user);}
      });
    break;
    default: console.log("[error] (createUser) : platform not implemented :",platform); cb(err,null); break;
  }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser2796352View Question on Stackoverflow
Solution 1 - JavascriptEMXView Answer on Stackoverflow