Express-session Secure Cookies not working

Viewed 16002

When not using secure cookie true setting, my app user login works fine. When I enable secure cookies, the login appears to go through fine, but it seems the cookie is not saved and the user is not logged in.

In other words, this works:

app = express();
app.use(session({
    secret: 'secret code',
    store: sessionStore,
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: false,
        maxAge: 5184000000 // 60 days
        }
}));

This does not work (user isn't able to log in):

app = express();
app.set('trust proxy');
app.use(session({
    secret: config.cookieSecret,
    store: sessionStore,
    resave: false,
    saveUninitialized: false,
    proxy: true,
    secureProxy: true,
    cookie: {
        secure: true,
        httpOnly: true,
        maxAge: 5184000000 // 60 days
        }
}));

Behind cloudflare and nginx. This is in my nginx config:

location ~ / {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass http://localhost:3000;
}

From what I read, I think it should work. What am I missing?

EDIT: I am running https with a valid ssl cert.

8 Answers

The combination of settings that worked for me:

  • Inside the nginx server configuration, add proxy_set_header X-Forwarded-Proto $scheme;
  • Inside the express-session configuration:

    server.use(
      session({
        proxy: true, // NODE_ENV === 'production'
        cookie: {
          secure: true, // NODE_ENV === 'production'
        },
        // everything else
      })
    );
    

Solution if using Heroku:

In Heroku, all requests come into the application as plain http but they have the header X-Forwarded-Proto to know whether the original request was http or https. That causes express to see non-ssl traffic and so it refuses to set a secure cookie when running on Heroku. Express will only send secure cookies over https. You have to tell express to trust the information in the X-Forwarded-Proto header, i.e. that the original request was over https, by enabling the 'trust proxy' setting. Before defining the cookie properties I put

app.set('trust proxy', 1);

Where 1 means trust the first proxy. 1 was good enough for me to set cookie: secure

Apache reverse proxy solution

For those looking for a potential solution and are using an Apache reverse proxy: Make sure to add the following directives above the ProxyPass directives inside the VirtualHost directive for the respective domain:

RequestHeader set X-Forwarded-Proto expr=%{REQUEST_SCHEME}
RequestHeader set X-Forwarded-SSL expr=%{HTTPS}

ProxyPass / http://localhost:8001/
ProxyPassReverse / http://localhost:8001/

This change allowed my cookies to persist.

Additionally, as others have pointed out, don't forget to add the following code to your production (or other hosted environments that use a proxy) express configuration:

  if (app.get('env') === 'production') {
      app.set('trust proxy', 1); // trust first proxy, crucial
  }

Lastly, make sure your cookie/session settings are set up correctly. Check out Stoyan Borov's answer for the minimum requirements. Here's how I set it up:

    app.use(session({
        cookie: {
            sameSite: 'lax', // lax or strict
            secure: process.env.NODE_ENV === 'production', // Crucial
            maxAge: 1000 * 60 * 60 * 24 * 30, // 30 days
        },
        proxy: true, // Crucial
        resave: false,
        saveUninitialized: true,
        secret: 'Gotta love cookies',
        store: yourSessionStore,
        rolling: true,
    }));

app.set('trust proxy', 1); worked like a charm

<VirtualHost *:80>
 ServerName xx.com
    ProxyPreserveHost On
<Location "/">

        ProxyPreserveHost On
        ProxyPass http://localhost:8080/
        ProxyPassReverse http://localhost:8080/
        RequestHeader set X-Forwarded-Port "443"
        RequestHeader set X-Forwarded-Proto "https"
</Location></VirtualHost>


Related