jdesboeufs / connect-mongo

MongoDB session store for Express
MIT License
1.96k stars 341 forks source link

Session created for every request #484

Open oshihirii opened 1 month ago

oshihirii commented 1 month ago

I've been troubleshooting this issue for several hours and have googled things like:

"express-session and connect-mongo create session on every request"

I can also see that several issues in this repo on the same topic have been resolved and closed.

I have tried all the solutions that others seem to have found resolved the issue, ie:

My initial problem started when I noticed an Azure AD auto-signin redirect back to my application's /redirect endpoint was creating a different session than the one that was created during the initial Azure AD B2C signin, and so I could see that two session object entries were added to my MongoDB Atlas database.

Then I added some console.log() statements to log out req.session values and sessions started being created in the database for every request (images, js, css etc).

I just thought I would post a chunk of my testing code below to ask if I am doing anything obviously wrong:

// this returns a mongodb client 
const mongodb_client = await mongodb_client_manager.open();
console.log("mongodb client connection established");

// connects to the rate limiter store database 
const rate_limits_db = mongodb_client.db('rate_limits');
console.log("rate limiter db connection established");

// connects to the express-session store database
const express_sessions_db = mongodb_client.db('express_sessions');
console.log("express sessions db connection established");

// was playing around with these - they will only create the index if it doesn't already exist
// await rate_limits_db.collection('rate_limits').createIndex({ expires: 1 }, { expireAfterSeconds: 0 });
// await express_sessions_db.collection('express_sessions').createIndex({ expires: 1 }, { expireAfterSeconds: 0 });

dotenv.config();

const PORT = process.env.PORT || 3000;

const app = express();
const httpServer = createServer(app);

// CORS configuration
const cors_options = {
  // replace with your allowed origin(s) - currently just testing in local environment  
  origin: 'http://localhost:3000', 
  // allow credentials (cookies, authorization headers, etc.)
  credentials: true, 
};

app.use(cors(cors_options));

// session configuration object
const session_config = {
  // secret used to sign the session ID cookie
  secret: process.env.SESSION_SECRET,
  // forces the session to be saved back to the session store, even if it was never modified during the request
  resave: false,
  // forces a session that is "uninitialized" to be saved to the store
  saveUninitialized: false,
  // cookie settings
  cookie: {
    // secure cookie only sent over HTTPS  
    // returns true if we are online, false if we are not
    secure: process.env.NODE_ENV === "production",  
    // 2 hours in milliseconds, if not set, the cookie will expire when the browser is closed
    // maxAge: 2 * 60 * 60 * 1000 
    httpOnly: true,
    sameSite: 'none',
    path: '/', // i was just testing these values out of desperation...
    domain: 'localhost'
  },
  // use connect-mongo to store sessions in MongoDB
  store: MongoStore.create({
    // use the existing mongodb client
    clientPromise: Promise.resolve(mongodb_client),   // it didn't like the client itself, so i wrapped it in a Promise.resolve()  
    // specify the database name
    dbName: 'express_sessions', 
    // specify the collection name
    collectionName: 'express_sessions', 
    // native is the default
    autoRemove: 'native', 
    // crypto: {
    //   secret: process.env.SESSION_STORAGE_CRYPTO_SECRET,
    //   algorithm: 'aes-256-gcm'
    // }
  })
};
app.use(session(session_config));

// global rate limiter - still testing  
const global_rate_limiter = rate_limit({
  // request window of 15 minutes
  windowMs: 15 * 60 * 1000, 
  // limit each IP to 1000 requests per request window 
  max: 1000, 
  store: new custom_rate_limiter_mongodb_store(rate_limits_db),
  keyGenerator: (req, res) => `global:${req.ip}`
});

// apply global rate limiter to all requests
app.use(global_rate_limiter);

app.use(
  helmet({
    crossOriginEmbedderPolicy: false,
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "https://cdnjs.cloudflare.com"],
        connectSrc: ["'self'"],
        styleSrc: [
          "'self'",
          "https://cdnjs.cloudflare.com",
          "https://fonts.googleapis.com",
        ],
        fontSrc: ["'self'", "https://fonts.gstatic.com"],
        imgSrc: ["'self'", "data:"],
        frameSrc: ["'self'"],
      },
    },
  })
);

app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(express.static("dist"));

app.use("/", routes);

I'd appreciate any tips that could lead to a solution.

Thank You.

oshihirii commented 1 month ago

I think I might have found a solution, atleast when testing in my local environment:

// CORS configuration
const cors_options = {
  origin: 'http://localhost:3000', // Replace with your allowed origin(s)
  credentials: true, // Allow credentials (cookies, authorization headers, etc.)
};

app.use(cors(cors_options));

app.use(cookieParser()); 

// session configuration object
const session_config = {
  // secret used to sign the session ID cookie
  secret: process.env.SESSION_SECRET,
  // forces the session to be saved back to the session store, even if it was never modified during the request
  resave: false,
  // forces a session that is "uninitialized" to be saved to the store
  saveUninitialized: false,
  // cookie settings
  cookie: {
    // secure cookie only sent over HTTPS if in production
    secure: false,  
    // maxAge: 2 * 60 * 60 * 1000 // 2 hours in milliseconds, if not set, the cookie will expire when the browser is closed
    httpOnly: true,
    path: '/'
  },
  // use connect-mongo to store sessions in MongoDB
  store: MongoStore.create({
    clientPromise: Promise.resolve(mongodb_client), // use the existing mongodb client
    dbName: 'express_sessions', // specify the database name
    collectionName: 'express_sessions', // specify the collection name
    autoRemove: 'native', // native is the default
    // crypto: {
    //   secret: process.env.SESSION_STORAGE_CRYPTO_SECRET,
    //   algorithm: 'aes-256-gcm'
    // }
  })
};
app.use(session(session_config));

I am still testing, but wanted to post back in case it saved someone the time in writing a response.

Obviously, I will need to modify the security related settings for production, but I will look at that next.

The good news is now only 1 session is being created in the MongoDB database.