36

I'm building a website using Node.js, Express, and Redis for session management. For whatever reason, if I have a session variable (isLoggedIn in this example), and I refresh the page, the variable doesn't get saved, however, if I call req.session.save() after setting the variable, it does get saved to Redis (redis-cli monitor shows this - not calling save() shows that the variable isn't there, whereas calling save() shows it).

I'm using this to set up and start the server:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var swig = require('swig');
var session = require('express-session')
var RedisStore = require('connect-redis')(session);

var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

// Configure the favicon first. This avoids other middleware from processing the request if we know the request is for the favicon.
app.use(favicon(__dirname + '/public/images/favicon.ico'));

// All other requests will require everything else.

// Set up the view engine.
app.set('view engine', 'html');
app.set('views', path.join(__dirname, '/views'));
app.engine('html', swig.renderFile);

// Set up our logger.
app.use(logger('dev'));

// Set up JSON parsing.
app.use(bodyParser.json());

// Set up encoded URL parsing.
app.use(bodyParser.urlencoded());

// Set up the cookie parser.
app.use(cookieParser('thedogsleepsatnight'));

// Set up our session store. This will use Redis as our session management.
app.use(session({
    resave: true,
    saveUninitialized: true,
    secret: "thedogsleepsatnight",
    store: new RedisStore()
}));

app.use(require('stylus').middleware(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);

And then, in that route, I have:

var express = require('express');
var router = express.Router();

router.get('/', function(req, res) {
    console.log(req.session.isLoggedIn);
    if (req.session.isLoggedIn) {
        console.log("Logged in!");
    } else {
        console.log("Not logged in");
    }

    res.render('index');
});

router.post('/login', function(req, res) {
    console.log("Going to set isLoggedIn. Currently: " + req.session.isLoggedIn);
    req.session.isLoggedIn = true;
    console.log("Set isLoggedIn. Currently: " + req.session.isLoggedIn);
});

module.exports = router;

From that, I should be able to navigate to /login, have the session set isLoggedIn to true, and that should save automatically to Redis. After that, heading to / should tell me that I'm logged in. Loading /login does set the variable, the second log shows that, but loading / says that I'm not logged in. redis-cli monitor shows

1414076171.241836 "setex" "sess:FIDJ9qDbX_0u9pzlC6VZEW76zZcyiPME" "86400" "{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"httpOnly\":true,\"path\":\"/\"}}"

upon saving, which doesn't include the isLoggedIn variable, but adding in req.session.save() shows:

1414076475.601644 "setex" "sess:FIDJ9qDbX_0u9pzlC6VZEW76zZcyiPME" "86400" "{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"httpOnly\":true,\"path\":\"/\"},\"isLoggedIn\":true}"

Any idea on why I have to call req.session.save() when all of the examples I've seen don't use it?

Chris Krycho
  • 3,125
  • 1
  • 23
  • 35
PuppyKevin
  • 2,967
  • 7
  • 25
  • 27
  • can you point out one of said examples? i would have expected needing to use .save() to push changes to storage. – Kevin B Oct 23 '14 at 15:11
  • Sure, http://tipstak.blogspot.com/2014/01/session-management-example-using-redis-on-express.html. He sets userId without calling save. In another SO question, http://stackoverflow.com/questions/14014446/how-to-save-and-retrieve-session-from-redis, it's explained that it should all be done automatically, where save() is there simply for manual saving. – PuppyKevin Oct 23 '14 at 15:16
  • Is there anything in the middleware you are using that could prevent or break the way express-session proxies the end method? https://github.com/expressjs/session/blob/master/index.js#L194 does `res.render` execute that method? – Kevin B Oct 23 '14 at 15:28
  • Just checked, I placed a console.log in the callback function for req.session.save in express-session's index.js file, and it's saying that it's saved successfully right after the value gets changed, but redis-cli monitor shows that the variable isn't there. – PuppyKevin Oct 23 '14 at 15:39
  • 1
    I did just notice something, when saving, isModified is returning false even though it's being executed after the variable change. – PuppyKevin Oct 23 '14 at 15:42
  • That was going to be my next question. :) `console.log(req.session)` to see whether or not it has the value you set (within express-session). – Kevin B Oct 23 '14 at 15:43
  • Added that in, and it shows that it's not actually added to the session. Going to set isLoggedIn. Currently: undefined Set isLoggedIn. Currently: true Got to end! Should save: true isModified: false { cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true } } Saving... Saved successfully. – PuppyKevin Oct 23 '14 at 15:45
  • Can you do a console.trace() to see what is above .end() in the stack? – Kevin B Oct 23 '14 at 15:49
  • Assuming that I should place it right after my isModified console.log call, http://pastebin.com/3xZWJqGX – PuppyKevin Oct 23 '14 at 15:57
  • you just have to change req.session values before res.end() – hingev May 22 '18 at 12:09

2 Answers2

114

Okay, so, I've figured it out. I'll put the answer here for anyone else that happens to get caught on this.

For GET requests, the server assumes that you're going to be sending data back, and will automatically save session data once the route is fully processed.

For POST requests (what I'm using), however, the same assumption isn't made. Session states are only saved in one of two conditions - either when data is being sent out (through res.send, res.redirect, etc.), or if you manually call req.session.save(). I was already calling /login from an AJAX request, I just wasn't returning anything if certain conditions were met. Having the server respond to the client with some data after setting the session variable fixed this.

thatWiseGuy
  • 384
  • 2
  • 3
  • 18
PuppyKevin
  • 2,967
  • 7
  • 25
  • 27
0

If the brilliant answer from @thatWiseGuy is not solving the problem ... here is a hint.

I called a axios POST request from document loading:

document.addEventListener('DOMContentLoaded', function () {
  axios.post('/do-magic', {
            data: data
        }, {withCredentials: true, credentials: 'include'}).then(res => {
            // even more magic
        }).catch(err => console.error(err));
}, false);

On server side, in the "do-magic" route, whenever I set a session and hit save, was not saved with the code below.

req.session.isMagic = true;
req.session.save();

I am not sure what is the reason behind, but when I invoked this call delayed, it was working properly and saving my session.

document.addEventListener('DOMContentLoaded', function () {   
    window.setTimeout(() => {
        axios.post('/do-magic', {data: data}, {withCredentials: true, credentials: 'include'}).then(res => {
                // even more magic
            }).catch(err => console.error(err));
    }, false);
}, 1000);
Botond Kopacz
  • 925
  • 8
  • 14