Joe C.
Joe C.

Reputation: 461

Express-session not sending secure cookie to localhost

Regular cookies via res.cookie(..., {secure: true}) works, but express-session does not. I made the following to demonstrate:

const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
    secret: 'qwerqwerqwer',
    resave: true,
    saveUninitialized: true,
    cookie: {
        secure: true,
    },
}));
app.get('/hello', (req, res) => {
    res.cookie('test', 'asdf', {secure: true});
    res.send('');
});
const PORT = 7000;
app.listen(PORT, () => {
    console.log(`backend listening on port ${PORT}`);
});

When I go to /hello, there is a Set-Cookie for test: asdf but no Set-Cookie for connect.sid. In other words, express-session is not sending the cookie at all; it is not a problem on the browser side. I also logged the response headers and it indeed does not include a Set-Cookie for connect.sid.

MDN says secure cookies work for localhost. It might not be true for chrome but they said it's true for Firefox, so I downloaded Firefox and tested on it, and I don't receive the connect.sid cookie, which also confirms that it's express-session's problem.

I think express-session is overstepping its authority and withholding the cookie for some reason (the secure attribute should be interpreted by the browser anyways). How do I fix this?

Upvotes: 1

Views: 2945

Answers (3)

Marrix
Marrix

Reputation: 343

I had a similar problem. I don't have HTTPS on localhost, so I couldn't use sameSite: 'none' (which requires HTTPS). Therefore, I chose sameSite: 'strict'.

However, my client on localhost 127.0.0.1:3000 was constantly rejected by my server's (127.0.0.1) cookie control in Chrome, even though it worked fine in Insomnia/Postman and in Firefox.

The problem was — perhaps obviously — that the server was running on http://127.0.0.1:xxxx, while my requests (GET POST PUT etc.) from the client were to http://localhost:xxxx. It worked fine in Firefox (and Insomnia/Postman), but as previously mentioned not in Chrome.

When I changed my client-apps requests from localhost to 127.0.0.1, it then also worked in Chrome. It appears that Chrome — unlike Firefox — does not consider localhost and 127.0.0.1 as 'sameSite'.

My session/cookie settings:

const expire = 1000 * 60 * 60 * 24 * 5 // 5 days
app.use( session( {
    name: process.env.SESSION_NAME, // sessionname in .env
    resave: true,
    rolling: false,
    saveUninitialized: false,
    store: MongoStore.create( { mongoUrl: process.env.DB_URL } ), // connectionstring in .env
    secret: 'process.env.SESSION_SECRET', // secret in .env
    cookie: {
        maxAge: expire,
        sameSite: "strict", //"none" requires https ... "lax" only works with GET
        secure: process.env.NODE_ENV === "production", // true when production/https otherwise false
        httpOnly: false
    }
} ) )

Upvotes: 0

Joe C.
Joe C.

Reputation: 461

I did some more digging and it turns out that this is a known issue with express-session: https://github.com/expressjs/session/issues/837

Basically, the docs around secure cookies has changed. It used to be that the server is supposed to withhold secure cookies over HTTP, and that is what express-session implemented. Now, it has changed to be the client's responsibility.

Upvotes: 2

Amine
Amine

Reputation: 81

Secure cookies are only sent through HTTPS so in development you should add a check to see if you're running on production or locally like bellow:

app.use(session({
    secret: 'qwerqwerqwer',
    resave: true,
    saveUninitialized: true,
    cookie: {
        secure: process.env.NODE_ENV === "production",
    },
}));

Upvotes: 1

Related Questions