Session: Session not persisting with nginx

Created on 26 Oct 2016  路  14Comments  路  Source: expressjs/session

I have following code in my application and the session is working fine if node is directly serving the request.

express = require("express")
session = require('express-session')

 var app = express();
app.set('trust proxy')
        app.use(session(
                {
                        secret: 'rdcfthv',
                        resave: true,
                    saveUninitialized: true
                }));

Now I need nginx (http / https) as a reverse proxy in front of node and I can still login but as soon the page is changed the session is lost.

Below is nginx conf.

location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme; 
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_read_timeout 5m;
        proxy_connect_timeout 5m;
        proxy_pass http://nodeserver;
        proxy_redirect off;
    }
}

My versions:
[email protected]
[email protected]
[email protected]

question

Most helpful comment

@gabeio Ec2 are just cloud instances like virtual containers . The Redis store is independently hosted on another server. Both ec2 instances connect to the same redis store, as I have other variables atomic counters and such that are same.

Yes, I saw the points made the following updates :

nginx final config :

           proxy_pass http://127.0.0.1:3020;
           proxy_set_header Upgrade $http_upgrade;
           proxy_set_header Connection 'upgrade';
           proxy_set_header Host $host;
           proxy_cache_bypass $http_upgrade;
           proxy_redirect off;
           proxy_http_version 1.1;
           proxy_set_header Host $host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_set_header X-Forwarded-Proto $scheme; 

Added in node app.js for express :

**app.set('trust proxy', true);**
app.use(session({
      secret: '++++++++',
      genid: function (req) {
        return util.generate_guid() + "-" + +new Date;
      },
      store: new redis_store(webapp_config[webapp_config.env].redis),
      **proxy: true,**
      resave: false,
      saveUninitialized: true,
      cookie: {
        maxAge: 60 * 24 * 60 * 60 * 1000
      }

Also I saw a comment to pass secret to cookie-parser. I am using cookie-parser but secret is set as above..

But as I mentioned the whole thing works on nginx with Https if I have a single server . As soon as the elb has a second server the cookie value for changes and the whole session screws up .

All 14 comments

237

Similar issue . session not persisting, but on multiple servers behind elb. Working fine on single nginx reverse proxy

What Session Store are you using? Or are you not using a database? If you are not using a database & you are using multiple sessions your issue lies in the fact that the sessions are not being shared between the instances of your app. In that case you may need 1 of 2 things:

  • switch to a session store (highly preferable)
  • tell nginx to proxy specifically based on the ip address (not preferable as if the user opens their browser on a different ip (think phones) they will be instantly logged out)

A few extra details will really also help:

  • Session-Store (or just Memory?)
  • A fully working example (doesn't have to be your entire app) that demonstrates the issue (this is important)

Hi
Had referenced #237 for the same @gabeio . Have given the details there, copy of which is given below:
Using a redis store for storing the session .

I have a load balancer over two ec2 instances running separate nodejs instances with a reverse proxy set up on nginx.
Funnily if I remove one server from the load balancer the session persists on a single server .

nginx setting :

http {
       server {
       listen 80;

       server_name example.com;

       location /loaderio-example.txt
       {
           alias /home/ubuntu/code/loader.io/loaderio-example.txt;
       }

       location / {
               if ($http_x_forwarded_proto != 'https') {
              rewrite ^ https://$host$request_uri? permanent;
          }
           proxy_pass http://127.0.0.1:3020;
           proxy_http_version 1.1;
           proxy_set_header Upgrade $http_upgrade;
           proxy_set_header Connection 'upgrade';
           proxy_set_header Host $host;
           proxy_cache_bypass $http_upgrade;
       }

   }

}

Option Parameters for session :

app.use(session({
      secret: '+++++++++++++++++++',
      genid: function (req) {
        return util.generate_guid() + "-" + +new Date;
      },
      **store: new redis_store(webapp_config[webapp_config.env].redis),**
      resave: false,
      saveUninitialized: true,
      cookie: {maxAge: 15 * 24 * 60 * 60 * 1000} 
    }));

@crazysal interesting I want to think that maybe e2 is creating 2 separate redis instances instead of using the same instance, is there anyway for you to check this? Sorry, I don't know e2 enough 馃槥.

Also as noted other places you should add proxy_set_header X-Forwarded-Proto $scheme; to your nginx config to allow node to know it's running over https and to tell it to accept https cookies instead of drop them. You also might need to add app.set('trust proxy') to tell express to trust the reverse proxy or more specifically app.set('trust proxy', 1) or more for better security.

@gabeio Ec2 are just cloud instances like virtual containers . The Redis store is independently hosted on another server. Both ec2 instances connect to the same redis store, as I have other variables atomic counters and such that are same.

Yes, I saw the points made the following updates :

nginx final config :

           proxy_pass http://127.0.0.1:3020;
           proxy_set_header Upgrade $http_upgrade;
           proxy_set_header Connection 'upgrade';
           proxy_set_header Host $host;
           proxy_cache_bypass $http_upgrade;
           proxy_redirect off;
           proxy_http_version 1.1;
           proxy_set_header Host $host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_set_header X-Forwarded-Proto $scheme; 

Added in node app.js for express :

**app.set('trust proxy', true);**
app.use(session({
      secret: '++++++++',
      genid: function (req) {
        return util.generate_guid() + "-" + +new Date;
      },
      store: new redis_store(webapp_config[webapp_config.env].redis),
      **proxy: true,**
      resave: false,
      saveUninitialized: true,
      cookie: {
        maxAge: 60 * 24 * 60 * 60 * 1000
      }

Also I saw a comment to pass secret to cookie-parser. I am using cookie-parser but secret is set as above..

But as I mentioned the whole thing works on nginx with Https if I have a single server . As soon as the elb has a second server the cookie value for changes and the whole session screws up .

What version of node are you using & what session version are you using because:

Note Since version 1.5.0, the cookie-parser middleware no longer needs to be used for this module to work. This module now directly reads and writes cookies on req/res. Using cookie-parser may result in issues if the secret is not the same between this module and cookie-parser.

node version 0.12.2
express @4.13.3
express-session @1.12.1

yeah you don't need the cookie-parser it might (doubted) be the issue.

Removed cookie parser. Still does not persist .

Hi @crazysal weird. Can you help us setup the same environment so we can debug? It's hard for us to figure it out without being able to debug the issue, which means we are just making blind suggestions, which may never resolve your issue. In order to really be able to help, we need your help so we can get an environment setup exactly the same as yours, if you can provide that information :)

Hi,
@dougwilson Please let me know what help you need in setting up the poc environment.
I narrowed down the error to the redis connection part of the module.
If I connect with redis using the "redis": "0.12.1" package and pass the client object it works beautifully.

But it breaks if the host and port are passed to the session function.

To elaborate
this works :

var redisConf =  {
      "hostname": "url_of_aws_redis_host",
      "port": 6379
    }  
var redis = require('redis');
var redisClient = redis.createClient(redisConf.port, redisConf.hostname);

app.use(session({
          secret: '*******#$$#$#$#',
          genid: function (req) {
            return util.generate_guid() + "-" + +new Date;  //util function to generate random id. 
          },
          store: new redis_store({
            "client" : redisClient 
          }),
          proxy: true,
          resave: false,
          saveUninitialized: true,
          cookie: {
            maxAge: 60 * 24 * 60 * 60 * 1000
          } 
        }));

But this does not {on multiple servers behind an Elb, works on a single server}:

var redisConf =  {
      "hostname": "url_of_aws_redis_host",
      "port": 6379
    }  
app.use(session({
          secret: '***&^&',
          genid: function (req) {
            return util.generate_guid() + "-" + +new Date; // util.generate_guid() is utility fn to generate uid
          },
          store: new redis_store(redisConf),          
          proxy: true,
          resave: false,
          saveUninitialized: true,
          cookie: {
            maxAge: 60 * 24 * 60 * 60 * 1000
          } 
        }));

I was testing on two t2 micro instances running the following :
Each has its own nginx server that resolves the elb request and backproxies it to port 3020 on which my node application runs. There is a single cname record that hits the elb and is routed to the nginx respectively.
node version 0.12.2 -->> updated to 6.9.1
express @4.13.3
express-session @1.12.1

Used pm2 to start/stop servers .

ps. If you could point me in the right direction would test on my end and give a pr, would love to contribute. Been intrigued much by this.

Your description sounds like an issue with either your redis module or whatever your redis_store module is. This module has no insight into what connection string you are passing into redis_store, so if that makes a difference, your issue wouldn't be related to this module.

connect-redis is the module used here : https://github.com/tj/connect-redis

@crazysal you'll want to fine an issue at https://github.com/tj/connect-redis if the root cause of your issue is how you happened to create your connect-redis store. Looking at the source code of connect-redis, your two calls above are likely not exactly the same, or you may not be using the same version of redis, which is likely your issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

G-Adams picture G-Adams  路  16Comments

antishok picture antishok  路  27Comments

simoami picture simoami  路  27Comments

renehauck picture renehauck  路  16Comments

alex55132 picture alex55132  路  22Comments