serializeUser in passport.js doesn't add user data into session in express.session

 I am building a MERN stack app, and there is a problem with authentication and passport.js during production when using render.com to deploy my site.

Now, the authentication, passport.js, and everything was working with localhost, but it doesn't work when deployed. This likely means that there is a session/cookie issue.

My app has two methods: localhost and google authentication. Both suffer the same issue: serializeUser is not putting user data into session. I know this because of the console.log statements that I put in my code. This is an example of what happened when I did google authentication:

Apr 5 10:44:08 PM  [0] Session Details: Session {
Apr 5 10:44:08 PM  [0]   cookie: {
Apr 5 10:44:08 PM  [0]     path: '/',
Apr 5 10:44:08 PM  [0]     _expires: 2023-04-09T02:44:08.190Z,
Apr 5 10:44:08 PM  [0]     originalMaxAge: 259200000,
Apr 5 10:44:08 PM  [0]     httpOnly: true,
Apr 5 10:44:08 PM  [0]     secure: true,
Apr 5 10:44:08 PM  [0]     sameSite: 'strict'
Apr 5 10:44:08 PM  [0]   }
Apr 5 10:44:08 PM  [0] }
Apr 5 10:44:08 PM  [0] <MESSAGE THAT CONTAINS DEVICE/BROWSER INFO THAT SENT REQUEST, I REDACTED THIS>
Apr 5 10:44:11 PM  [0] Session Details: Session {
Apr 5 10:44:11 PM  [0]   cookie: {
Apr 5 10:44:11 PM  [0]     path: '/',
Apr 5 10:44:11 PM  [0]     _expires: 2023-04-09T02:44:11.169Z,
Apr 5 10:44:11 PM  [0]     originalMaxAge: 259200000,
Apr 5 10:44:11 PM  [0]     httpOnly: true,
Apr 5 10:44:11 PM  [0]     secure: true,
Apr 5 10:44:11 PM  [0]     sameSite: 'strict'
Apr 5 10:44:11 PM  [0]   }
Apr 5 10:44:11 PM  [0] }
Apr 5 10:44:11 PM  [0] Attempting google callback...
Apr 5 10:44:11 PM  [0] Google callback succesful.
Apr 5 10:44:11 PM  [0] serialize user is working
Apr 5 10:44:11 PM  [0] user object is: {
Apr 5 10:44:11 PM  [0]   _id: new ObjectId("<long string of id, redacted>"),
Apr 5 10:44:11 PM  [0]   googleId: '<long string of id, redacted>',
Apr 5 10:44:11 PM  [0]   email: 'pleasehelpmestackoverflow@gmail.com',
Apr 5 10:44:11 PM  [0]   __v: 0
Apr 5 10:44:11 PM  [0] }
Apr 5 10:44:11 PM  [0] user_id object is: <long string of id, redacted>

serializeUser works, and it does get the right information from google authentication (as shown from the "user object is" console.log above Below is my code for passport.js:

const express = require("express");

// require dotenv
require('dotenv').config({ path: './.env' });

// require middlewares
const compression = require("compression");
const helmet = require("helmet");
const RateLimit = require("express-rate-limit");
const logger = require("morgan");
const cors = require("cors");
const session = require("express-session"); 
const MongoStore = require("connect-mongo");
var cookieParser = require('cookie-parser')
const passport = require("passport");

// require configuration stuff
const InitiateMongoServer = require("./db");
const passportStrategy = require("./passport");

// require routes
const auth = require("./routes/auth");
const user = require("./routes/user");

/* setup stuff */
InitiateMongoServer();
const app = express();
const PORT = process.env.PORT || 4000;

// the server will handle at maximum 60 requests per minut
const limiter = RateLimit({
  windowMs: 1 * 60 * 1000, // 1 minute
  max: 60,
});

app.use(cookieParser());
app.use(session({
  secret: 'imnotgivingawayprivateinformationinstackoverflow', 
  resave: false,
  saveUninitialized: true,
  store: MongoStore.create({ mongoUrl: process.env.MONGODB_CONNECT_URL }),
  cookie: {
    secure: true, // set to true for production https
    httpOnly: true,
    sameSite: 'strict',
    maxAge: 72 * 60 * 60 * 1000 // 3 days
  }
}));


app.use(passport.initialize());
app.use(passport.session());

const printSessionDetails = (req, res, next) => {
  console.log('Session Details:', req.session);
  next();
};

// app.use(refreshSession);
app.use(printSessionDetails);

// middleware stuff
app.use(logger('combined'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(helmet());
app.use(compression());

// updated cors to allow cookies to be set in cross domain
app.use(cors(function (req, cb) {
  let corsOptions;
  corsOptions = {
    origin: process.env.CLIENT_URL,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization', 'Cookie'],
    credentials: true
  };
  cb(null, corsOptions);
}), function (req, res, next) {
  res.header('Access-Control-Allow-Credentials', true);
  next();
});


// Middleware for login
app.use("/auth", auth);
app.use("/user", user);

app.listen(PORT, (req, res) => {
  console.log(`Server starting at PORT ${PORT}`);
});

module.exports = app;

Below is my code for server.js:

const express = require("express");

// require dotenv
require('dotenv').config({ path: './.env' });

// require middlewares
const compression = require("compression");
const helmet = require("helmet");
const RateLimit = require("express-rate-limit");
const logger = require("morgan");
const cors = require("cors");
const session = require("express-session"); 
const MongoStore = require("connect-mongo");
var cookieParser = require('cookie-parser')
const passport = require("passport");

// require configuration stuff
const InitiateMongoServer = require("./db");
const passportStrategy = require("./passport");

// require routes
const auth = require("./routes/auth");
const user = require("./routes/user");

/* setup stuff */
InitiateMongoServer();
const app = express();
const PORT = process.env.PORT || 4000;

// the server will handle at maximum 60 requests per minut
const limiter = RateLimit({
  windowMs: 1 * 60 * 1000, // 1 minute
  max: 60,
});

app.use(cookieParser());
app.use(session({
  secret: 'imnotgivingawayprivateinformationinstackoverflow', 
  resave: false,
  saveUninitialized: true,
  store: MongoStore.create({ mongoUrl: process.env.MONGODB_CONNECT_URL }),
  cookie: {
    secure: true, // set to true for production https
    httpOnly: true,
    sameSite: 'strict',
    maxAge: 72 * 60 * 60 * 1000 // 3 days
  }
}));


app.use(passport.initialize());
app.use(passport.session());

const printSessionDetails = (req, res, next) => {
  console.log('Session Details:', req.session);
  next();
};

// app.use(refreshSession);
app.use(printSessionDetails);

// middleware stuff
app.use(logger('combined'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(helmet());
app.use(compression());

// updated cors to allow cookies to be set in cross domain
app.use(cors(function (req, cb) {
  let corsOptions;
  corsOptions = {
    origin: process.env.CLIENT_URL,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization', 'Cookie'],
    credentials: true
  };
  cb(null, corsOptions);
}), function (req, res, next) {
  res.header('Access-Control-Allow-Credentials', true);
  next();
});


// Middleware for login
app.use("/auth", auth);
app.use("/user", user);

app.listen(PORT, (req, res) => {
  console.log(`Server starting at PORT ${PORT}`);
});

module.exports = app;

Below is my code for controller for handling auth stuff (routing is in another file)

const passport = require("passport");

exports.success = (req, res) => {
    if (req.user) {
        res.redirect(process.env.CLIENT_URL + "/checklist");
    } else {
        res.redirect(process.env.CLIENT_URL + "/login");
    }
};

exports.fail = (req, res) => {
    res.redirect(process.env.CLIENT_URL + "/login");
};

exports.success2 = (req, res) => {
    if (req.user) {
        res.json({ success: true });
    } else {
        res.status(401).json({ success: false, message: "Something went wrong. Please try again."});
    }
};

exports.fail2 = (req, res, errorMessage) => {
    res.status(401).json({ success: false, message: errorMessage});
};


// (step 1) authenticates with google page
exports.google = passport.authenticate(
    "google", 
    { scope: [ 'email', 'profile' ]}
);
// (step 2) log in or create account
exports.google_callback = (req, res, next) => {
    passport.authenticate("google", (err, user, info) => {
        if (err) {
            return exports.fail(req, res, next);
        }
        if (!user) {
            return exports.fail(req, res, next);
        }
        req.logIn(user, (err) => {
            if (err) {
                return exports.fail(req, res, next);
            }
            return exports.success(req, res, next);
        });
    })(req, res, next);
};

exports.signup = (req, res, next) => {
    console.log("controller for signup calling");
    passport.authenticate("local_signup", (err, user, info) => {
        console.log('Passport authenticate callback');
        if (err) {
            console.error('Error:', err);
            return res.status(500).json({ success: false, message: "Something went wrong. Please try again." });
        }
        if (!user) {
            console.log('No user:', info.message);
            return res.status(401).json({ success: false, message: info.message });
        }
        req.logIn(user, (err) => {
            if (err) {
                console.error('Error logging in:', err);
                return res.status(500).json({ success: false, message: "Something went wrong. Please try again." });
            }
            console.log('User logged in');
            return res.json({ success: true });
        });
    })(req, res, next);

};
  
exports.login = (req, res, next) => {
    passport.authenticate("local_login", (err, user, info) => {
        if (err) {
            return exports.fail2(req, res, info.message, next);
        }
        if (!user) {
            return exports.fail2(req, res, info.message, next);
        }
        req.logIn(user, (err) => {
            if (err) {
                return exports.fail2(req, res, info.message, next);
            }
            return exports.success2(req, res, next);
        });
    })(req, res, next);
};

exports.logout = (req, res) => {
    console.log('attempting to log out of session');
    try {
        req.session = null;
        res.json({ loggedOut: true });
    } catch (err) {
        res.json({ loggedOut: false });
    }
};

exports.authenticationCheck = (req, res, next) => {
    console.log("req.user is: " + req.user);
    console.log("req.session is: " + req.session);
    console.log("Checking authentication status: ");
    if (req.isAuthenticated()) {
        console.log("Authentication Check is working");
        res.json({ isAuthenticated: true });
    } else {
        console.log("Authentication Check is not working");
        res.json({ isAuthenticated: false });
    }
}
  1. I have tried experimenting between cookie-session and express-session. I believe earlier in the day, using cookie-session somehow made it kind of work (passport info was being stored in session), but later I believe the same code stopped working. I honestly don't know which one to choose, but cookie-session was giving me an empty session later on, so I decided to use express-session.

  2. I tried to check if my environmental variables, URL connections, or google console settings had an issue and had the right domain/URL. All of them were.

  3. I changed serializeuser and deserializeUser many times using advice from chatGPT. I don't even know if I can list all of it, but my passport.js and server.js code is a mess.

  4. The code I have works on localhost on my device. When I deploy to render (a static service for frontend and a web service for backend), serializeUser fails to store into session.


It seems like there might be an issue with the cookie-parser middleware, which is required by passport.session() to parse cookies. Try moving cookie-parser above the session middleware like this:

javascript
const express = require("express"); // require dotenv require('dotenv').config({ path: './.env' }); // require middlewares const compression = require("compression"); const helmet = require("helmet"); const RateLimit = require("express-rate-limit"); const logger = require("morgan"); const cors = require("cors"); const session = require("express-session"); const MongoStore = require("connect-mongo"); const cookieParser = require('cookie-parser'); const passport = require("passport"); // require configuration stuff const InitiateMongoServer = require("./db"); const passportStrategy = require("./passport"); // require routes const auth = require("./routes/auth"); const user = require("./routes/user"); /* setup stuff */ InitiateMongoServer(); const app = express(); const PORT = process.env.PORT || 4000; // the server will handle at maximum 60 requests per minut const limiter = RateLimit({ windowMs: 1 * 60 * 1000, // 1 minute max: 60, }); app.use(cookieParser()); app.use(session({ secret: 'imnotgivingawayprivateinformationinstackoverflow', resave: false, saveUninitialized: true, store: MongoStore.create({ mongoUrl: process.env.MONGODB_CONNECT_URL }), cookie: { secure: true, // set to true for production https httpOnly: true, sameSite: 'strict', maxAge: 72 * 60 * 60 * 1000 // 3 days } })); app.use(passport.initialize()); app.use(passport.session()); const printSessionDetails = (req, res) => { console.log("Session Details:", req.session); console.log(req.session.passport); }; app.get("/", printSessionDetails, (req, res) => { res.send("Hello, World!"); }); /* API routes */ app.use("/api/auth", auth); app.use("/api/user", user); app.listen(PORT, (req, res) => { console.log(`Server Running on PORT ${PORT}`); });

Also, make sure that the cookie-parser middleware is not included in any other file, as it should only be included once in the main file.

If this does not solve the issue, you may want to check if the cookies are being sent and received correctly in your deployment environment. You can use the browser's developer tools to inspect the cookies and their values.

Comments