0

I use NodeJS and express-session to store session information on the database, per Log user out of previous sessions . The relevant code is in the main script file:

const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);

// Initialize mongodb session storage to remember users.
const store = new MongoDBStore({
  uri: config.mongoUri,
  // The 'expires' option specifies how long after the last time this session was used should the session be deleted.
  // Effectively this logs out inactive users without really notifying the user. The next time they attempt to
  // perform an authenticated action they will get an error. This is currently set to 3 months (in milliseconds).
  expires: max_session_ms,
});


// Enable sessions using encrypted cookies
app.use(cookieParser(config.secret));
app.use(
  session({
    cookie: {
      // Specifies how long the user's browser should keep their cookie, probably should match session expiration.
      maxAge: max_session_ms
    },
    store: store,
    secret: config.secret,
    signed: true,
    resave: true,
    saveUninitialized: true,
    httpOnly: true,  // Don't let browser javascript access cookies.
    secure: config.secureCookies, // Only use cookies over https in production.
  })
);

and in the routes file:

passport.use('user-otp', new CustomStrategy(
  // code to validate one-time password.
));

The problem is that this method also stores cookies on the browser for users not logged in. Here is an example for a visit to a public page:

cookies stored

I want to store cookies only after creating an account or logging in, where the user consented to cookie policies, and not on public pages to avoid the "cookie consent box" required by GDPR.

How can I store cookies only after a browser logs in?

update

I followed the suggestions in the answer and eliminated two cookies. I stop the server, eliminate localhost's cookies, start the server, make a public page request, and I still have one cookie on public pages. This is the session data on the server's database:

> db.sessions.find().pretty()
{
    "_id" : "DObp-FFNJGLD5c5kLKWfkCaEhfWHtWpo",
    "expires" : ISODate("2022-03-03T19:41:29.807Z"),
    "session" : {
        "cookie" : {
            "originalMaxAge" : 7776000000,
            "expires" : ISODate("2022-03-03T19:41:29.807Z"),
            "secure" : null,
            "httpOnly" : true,
            "domain" : null,
            "path" : "/",
            "sameSite" : null
        },
        "flash" : {
            
        }
    }
}

and the browser shows this cookie:

browser cookie

I get the same results when I remove PassportJS from the app and when I set sameSite to "strict".

second update

As the database session has an empty flash field, I suspected it was due to flash messages. I removed this code:

const flash = require("express-flash");
app.use(flash());

and now the server does not store a cookie for visits to public pages. I use flash messages in notifications for users who login and also for public page visitors, e.g. when a page is no longer available.

So the question becomes: is it possible to use flash messages server-side only, from one handler to another, and not store a cookie?

miguelmorin
  • 5,025
  • 4
  • 29
  • 64

2 Answers2

1

Going off the documentation, the saveUninitialized flag should be getting set to false -

Forces a session that is "uninitialized" to be saved to the store. A session is uninitialized when it is new but not modified. Choosing false is useful for implementing login sessions, reducing server storage usage, or complying with laws that require permission before setting a cookie. Choosing false will also help with race conditions where a client makes multiple parallel requests without a session. The default value is true, but using the default has been deprecated, as the default will change in the future. Please research into this setting and choose what is appropriate to your use-case.

https://github.com/expressjs/session#saveuninitialized

David
  • 1,007
  • 7
  • 14
  • That eliminates two cookies for public pages and I still have one, `connect.sid`. Do you also have that? – miguelmorin Dec 03 '21 at 18:29
  • 1
    Hm, that seems strange, but might be related to the `resave` flag. Little unclear how that functions - https://github.com/expressjs/session#resave – David Dec 03 '21 at 19:06
  • I read the link and set `resave: false`. I cleared `localhost`'s cookies and restarted the server. I still get a `connect.sid` cookie set by `localhost`. – miguelmorin Dec 03 '21 at 19:22
  • 1
    I just saw this note at bottom of the `saveUninitialized` description - `Note if you are using Session in conjunction with PassportJS, Passport will add an empty Passport object to the session for use after a user is authenticated, which will be treated as a modification to the session, causing it to be saved. This has been fixed in PassportJS 0.3.0` You could test if it is related to Passport by temporarily disabling it? – David Dec 03 '21 at 19:29
  • I commented in `app.js` the three lines with `passport`: `const passport = require('passport');`, `app.use(passport.initialize());` and `app.use(passport.session());`. I still get the same result. – miguelmorin Dec 03 '21 at 19:42
  • From your suggestion of commenting `passport` and the session field `flash`, I found the culprit in flash messages and added an update. – miguelmorin Dec 04 '21 at 12:15
1

Configure your app.use() for cookie/sessions after creating your static page routes. Express evaluates middleware and routes in the order of their declaration.

Basically do this:

// Create express app

// Declare app.use(express.static) routes

// Declare app.use(cookie) and app.use(session) routes

// Declare routes that need to use cookies/sessions

Because in this case the static content routes are declared before the cookie/session middleware the cookie middleware is not applied to the static routes.

If your route structure is more complex than this, I'd recommend making your session logic a middleware function that you can export from a dedicated file (middlware.js?). You can then require it and add the cookie/session middleware as needed inside specific routers (after declaring any static paths in that router).

Rob Riddle
  • 3,544
  • 2
  • 18
  • 26
  • Yes, this works. One disadvantage: if a user visits a public page, it will look as if they were signed out, e.g. they would see a "login" button even if a cookie is sent. For my use case, I don't need consent because I'll only store technical cookies, such as flash messages. See https://stackoverflow.com/questions/70237367/store-cookies-only-for-users-who-agreed-to-terms/70324133?noredirect=1#comment124359141_70324133 But if I were starting from scratch, I would do it this way and implement user area in a sub-domain. – miguelmorin Dec 16 '21 at 11:37