Search code examples
node.jssessionherokurediscors

Express session resets session on every request


I have a VueJS project that uses axios to call a server on another domain. On this server, I need to save a few values in the session so they don't need to be looked up on every request.

The server is NodeJS and runs on Heroku and I'm using Redis for memory storage. I can successfully save data to the session, but on every new request, the system creates a new session with a new ID so I can't access the values saved during the previous request.

EDIT After updating the code based on a number of suggestions, I can see the following error in the Network console on the session cookie:

Preflight Invalid Allow Credentials

EDIT 2 I was able to resolve the Preflight Invalid Allow Credentials by adding "credentials: true" to the corsOptions. This resolves the error I was seeing in network on the session, but I am still getting a new session ID for every request.

Code on the server:

const express = require('express');
const app = express();

const cors = require('cors');
var corsWhitelist = ['http://127.0.0.1:8080','http://127.0.0.1:8081']
var corsOptions = {
  origin: function (origin, callback) {
    if (corsWhitelist.indexOf(origin) !== -1) {
        callback(null, true)
    } else {
        callback(new Error('Not allowed by CORS - '+origin))
    }
  },
credentials: true
}

let REDIS_URL = process.env.REDIS_URL;
var Redis = require('ioredis');

const session = require('express-session');
const cookieParser = require('cookie-parser');
const RedisStore = require('connect-redis')(session);
const sessionClient = new Redis(REDIS_URL)

sessionClient.on('error', function (err) {
    console.log('could not establish a connection with redis. ' + err);
  });
sessionClient.on('connect', function (err) {
    console.log('connected to redis successfully');
  });

app.set('trust proxy', 1)
app.use(cookieParser());
app.use(session({
    store: new RedisStore({ client: sessionClient }),
    secret: 'someSecret',
    resave: false,
    saveUninitialized: true,
    cookie: {
        secure: false,
        httpOnly: false,
        maxAge: 1000 * 60 * 10
    }
}))

app.use(cors(corsOptions));
app.options('*', cors(corsOptions))
// Add headers
app.use(function (req, res, next) {
    if (corsWhitelist.indexOf(req.headers.origin) !== -1) {
        res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
        res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
        res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
        res.setHeader('Access-Control-Allow-Credentials', 'true');
    }
    next();
});

const getUser = async function(req, res, next) {
    if (!req.session.user) {
        req.session.user = "test@example.com"
        req.session.save()
    }
    next()
  }

app.get('/session', getUser, (req, res) => {
    // get the session id
    console.log('session id:', req.session.id)
    // the session will be automatically stored in Redis with the key prefix 'sess:'
    const sessionKey = `sess:${req.session.id}`;
    // let's see what is in there
    client.get(sessionKey, (err, data) => {
      console.log('session data in redis:', data)
    })
    res.status(200).send('OK');
  })

Method on VueJS:

getSession: async function () { 
  axios({
    url: 'https://server.example.com/session',
    withCredentials: true,
  }).then(res => {
    console.log(res)
  })
},

Solution

  • There were a number of changes required to make it work:

    The preflight settings were being set twice, so in the code below, I needed to remove the second line:

    app.use(cors(corsOptions));
    app.options('*', cors(corsOptions)) //delete this
    

    The headers I was trying to set under "// Add headers" didn't make it to the preflight request, so instead I needed to add "credentials: true" to the corsOptions and remove the code under "// Add headers":

    var corsOptions = {
      origin: function (origin, callback) {
        if (corsWhitelist.indexOf(origin) !== -1) {
            callback(null, true)
        } else {
            callback(new Error('Not allowed by CORS - '+origin))
        }
      },
      credentials: true
    }
    

    Last but not least, the cookie settings in the session definition weren't working for a cross-domain request. Specifically, "sameSite: 'none'" and "secure: true" were necessary. Result looks like this:

    app.use(session({
        store: new RedisStore({ client: client }),
        secret: 'someSecret',
        resave: false,
        saveUninitialized: true,
        cookie: {
            secure: true,
            httpOnly: false,
            sameSite: 'none',
            maxAge: 1000 * 60 * 10
        }
    }))