Express Session Does Not Save After Redirect

In my production app, saving data to a session then redirecting is completely unreliable. A console.log after saving the session shows the data has been attached. Then on redirect, another console.log shows that the session has been reset. Every 3-5 tries, the session will persist across the redirect, but it is mostly unreliable. In my development app this code works flawlessly…

• I’ve tried changing the version of express-session
• I’ve tried moving the static folder above the session middleware in server.js
• I’ve tried using req.session.save()

UPDATE ******
This is a known issue with the session middleware: https://github.com/expressjs/session/pull/69

Here is my server.js

// Module Dependencies
var express = require('express');
var app = express();
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var session = require('express-session');
var favicon = require('serve-favicon');
var methodOverride = require('method-override');

// Set Environment from ENV variable or default to development
var env = process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var config = require('./config/config');

// Set Port
var port = process.env.PORT || config.app.port;

// Connect to our MongoDB Database
// mongoose.connect(config.db);

// set the static files location /public/img will be /img for users
app.use(express.static(__dirname + '/public'));

// Express Session
app.use(session({
    secret: 'asfasfa3asfa',
    resave: true,
    saveUninitialized: true,
    cookie: {
        secure: false,
        maxAge: 2160000000
    }
}));

// Favicon
app.use(favicon(__dirname + '/public/img/favicon.ico'));

// Set Jade as the template engine
app.set('views', './app/views');
app.set('view engine', 'jade');

// Get req.body as JSON when receiving POST requests
app.use(bodyParser.json()); // parse application/json 
app.use(bodyParser.json({
    type: 'application/vnd.api+json'
})); // parse application/vnd.api+json as json
app.use(bodyParser.urlencoded({
    extended: true
})); // parse application/x-www-form-urlencoded

// override with the X-HTTP-Method-Override header in the request. simulate DELETE/PUT
app.use(methodOverride('X-HTTP-Method-Override'));

// routes ==================================================
require('./app/routes')(app); // pass our application into our routes

// start app ===============================================
app.listen(port);
console.log('****** App is now running on port ' + port + ' ******'); // shoutout to the user
exports = module.exports = app; // expose app

Here is the controller where the session is being saved:

// Module dependencies.
var config = require('../../config/config');

// Render Either Home Page or Dashboard Page If User is Logged In
var index = function(req, res) {

    console.log("Session At Home Page: ", req.session)

    if (req.session.user) {
        res.render('dashboard');
    } else {
        res.render('home');
    }
};


// Handle Authentication Callback
var callback = function(req, res) {
    // Get Access Token via Service-SDK
    Service.getAccessToken(req, function(error, tokens) {

        if (error) {
            console.log(error);
            return res.redirect('/');
        }

        // Otherwise, Save User Data & API Tokens To Session
        req.session.regenerate(function(err) {
            req.session.user = tokens.user_id;
            req.session.access_token = tokens.access_token;
            req.session.client_token = tokens.client_token;
            req.session.save(function(err) {
                console.log("Session Before Redirect: ", req.session);
                res.redirect('/');
            })
        });
    });
};

module.exports = {
    index: index,
    callback: callback
};

My Routes

app.get('/auth/service/callback', application.callback)
    app.get('/logout', application.logout);
    app.get('/', application.index);

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

There is a conflict between the livereload and the express-session in the Express 4. You can read more about it here https://github.com/expressjs/cookie-session/issues/14

But in short, the livereload must be called AFTER the session. As this:

var express     = require("express");
var http        = require("http");
var session     = require("express-session");
var livereload  = require("connect-livereload");

var app = express();
app.set("port", 9090);

/**
 * First you must config the session
 */
app.use(session({
    secret: "keyboardcat",
    name: "mycookie",
    resave: true,
    saveUninitialized: true,
    cookie: { 
        secure: false,
        maxAge: 6000000
    }
}));
/**
 * Then you can config the livereload
 */
app.use(livereload())

/**
 * This simple url should create the cookie. 
 * If you call the livereload first, the cookie is not created.
 */
app.get("/",function(req,res){
    req.session.name = "blaine";
    res.end("ok");
});

/**
 * If you call the livereload first, the session will always return nothing
 * because there is no cookie in the client
 */
app.get("/me",function(req,res){
    var name = req.session.name;
    var message = "Hello [" + name + "]!";
    res.end(message);
});
http.createServer( app ).listen(
    app.get("port"),
    function(){
        console.log("server is running in port ", app.get("port"));
    }
)

If you call the livereload BEFORE the session config, all will seems to work but the cookie will not persist. This was a tricky bug and I lost a full day into it. I hope this help somebody.

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply