`req.session.user` is `undefined` when using `express-session` and `connect-mongo` - node.js

I've created a react app that runs on port:3000 and an express app that runs on port:3001.
I am using express-session and connect-mongo to handle user sessions. When I set a user session in /login it was recorded in MongoDB as expected. But when I query for req.session.user later in a different path/route, for example /channels it returns undefined.
This is how my app.js looks
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const {Server} = require("socket.io");
const io = new Server(server);
const port = process.env.PORT || 3001;
const cors = require("cors");
const path = require('path');
const session = require('express-session');
const bodyParser = require('body-parser');
const oneDay = 1000 * 60 * 60 * 24;
const MongoStore = require('connect-mongo');
const md5 = require('md5');
const hash = '=:>q(g,JhR`CK|acXbsDd*pR{/x7?~0o%?9|]AZW[p:VZ(hR%$A5ep ib.&BLo]g';
app.use(session({
secret: hash,
saveUninitialized: false,
resave: false,
store: MongoStore.create({
mongoUrl: 'mongodb://localhost:27017/chat',
ttl: 14 * 24 * 60 * 60 // = 14 days. Default
})
}));
app.use(
cors({
origin: true,
credentials: true,
optionsSuccessStatus: 200
}));
// create application/json parser
const jsonParser = bodyParser.json({limit: '50mb'})
// create application/x-www-form-urlencoded parser
const urlencodedParser = bodyParser.urlencoded({limit: '50mb', extended: false})
app.post('/login', jsonParser, (req, res) => {
db.users.find({email: req.body.email}).toArray().then(user => {
if (user.length < 1) {
res.send({success: false, error: 'NOT_FOUND', message: 'Invalid login info!'});
} else {
user = user[0];
if (user.password === req.body.password) {
db.users.updateOne({"email": user.email}, {$set: {"online": "1"}}).then(ret => {
req.session.user = user.email;
req.session.userdata = user;
res.json(<=user data=>);
});
}
}
})
});
app.post('/channels', async (req, res) => {
if (!req.session.user) {// THIS IS ALWAYS TRUE; EVEN AFTER SUCCESSFUL LOGIN
res.json({logout: true});
return;
}
const user = JSON.parse(req.session.userdata);
const channels = db.channels.find({contacts: {$all: [user._id]}}).toArray().then(channels => {
let allch = {};
channels.map(function (channel) {
channel.id = channel._id.toString();
channel.notif = 0;
allch[channel.id] = channel;
});
res.json(allch);
});
});

When You fetch from front-end for specific route, don't forget to include in options: "credentials: "include" ", like here:
const options = {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(searchInput),
credentials: "include",
};
fetch("http://localhost:4000/sendSearchInput", options)
.then((res) => res.json())
.then((data) => {
console.log(data);
});
Edit:
Note - This should be included in each request from the client that either sets or reads the 'express-session' middleware (req.session.x).
(Not just reads)

I think you will need to call req.session.save() yourself when you want to update the session store with the current data.

Related

req.cookies and req.signedcookies are empty

I'm storing cookies on my server, but when I tried to access them. Object Returns Null.
code I'm using to store my cookies. This is done when I'm logging in!
res.cookie("accessToken", accessToken, {
httpOnly: true,
secure: true,
expires: new Date(Date.now() + oneDay),
});
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: true,
expires: new Date(Date.now() + oneDay),
});
index.ts
const dotenv = require("dotenv");
dotenv.config();
const PORT = process.env.PORT || 3001;
const cookies = require("cookie-parser");
const express = require("express");
const app = express();
const cors = require("cors");
app.use(cors());
app.use(express.json());
app.use(cookies());
import dbConnect from "./db/connect";
import adminAuthRoter from "./routes/admin/adminAuthRouter";
app.get("/", (req: any, res: any) => {
res.send("Hello World");
});
app.use("/api/v1/adminauth", adminAuthRoter);
const start = async () => {
try {
await dbConnect(process.env.MONGODB_URI);
app.listen(PORT, () =>
console.log(`Server is listening on port ${PORT}...`)
);
} catch (error) {
console.log(error);
}
};
start();
When I tried to console.log(req.cookies) or console.log(req.signedCookies) my response is empty. But when I see my Postmon cookies there are cookies stored
Postmon Cookie Reponse Image
What may be the issue here?

Server not functioning in mern heroku deployment

I have a semi successful deployment to heroku but the calls to the server are saying CONNECTION REFUSED and I can't figure it out.
I can hit the route and it returns correctly in postman but not in production
Below is the services file (the ones with /api in front are the ones being called)enter image description here
`import http from "../utils/http-common";
class CountriesService {
getAll() {
return http.get("/api/country");
}
getAllCountries() {
return http.get("/country/getAll");
}
getScroll(skip) {
return http.get(`/country?skip=${skip}`);
}
get(id) {
return http.get(`/country/${id}`);
}
create(countryForm) {
return http.post("/country/new", countryForm);
}
edit(id, values) {
return http.put(`/country/${id}`, values);
}
delete(id) {
return http.delete(`/country/${id}`);
}
}
export const getPostsPage = async (pageParam = 1, options = {}) => {
const response = await http.get(`/api/country?_page=${pageParam}`, options)
return response.data
}
export default new CountriesService();
`
This is the http-common
import axios from "axios";
var url;
process.env.REACT_APP_NODE_ENV === "production"
? (url = "")
: (url = "http://localhost:5000/");
export default axios.create({
baseURL: `${url}`,
headers: {
'Content-Type': 'application/json',
},
withCredentials: true,
});
this is the server file on the backend with a proxy from the frontend to localhost 5000
const path = require('path');
require("dotenv").config();
const express = require("express");
const app = express();
const mongoose = require("mongoose");
const cors = require("cors");
var morgan = require("morgan");
const MongoDBStore = require('connect-mongo');
const mongoSanitize = require('express-mongo-sanitize');
const helmet = require('helmet');
// Models
const User = require("./models/user.js");
// Passport Config
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const flash = require("connect-flash");
const session = require("express-session");
const cookieParser = require("cookie-parser");
// const { MongoClient } = require("mongodb");
const BodyParser = require("body-parser");
const { storage } = require("./imageupload/cloudinary");
const userAuthRoute = require("./routes/user.js");
const reviewRoute = require("./routes/review.js");
const countryRoute = require("./routes/country.js");
const cityRoute = require("./routes/city.js");
const activityRoute = require("./routes/activity.js");
const restaurantRoute = require("./routes/restaurant.js");
const dishesRoute = require('./routes/dishes.js');
const outdoorsRoute = require('./routes/outdoors.js');
const apiRoutes = require("./routes/api.js");
const cityReviewRoute = require("./routes/cityReviews.js");
const foodRoute = require('./routes/food.js');
const landmarkRoute = require('./routes/landmark.js');
const searchRoute = require('./routes/search.js');
const contactRoute = require('./routes/contact.js');
const db_url = process.env.DB_URL;
const PORT = process.env.PORT || 5000;
const secret = process.env.SESSION_SECRET;
// const client = new MongoClient(process.env.DB_URL);
const corsOptions = {
origin: [
"http://localhost:3000",
"http://localhost:5000",
"https://geopilot.herokuapp.com",],
credentials: true,
optionSuccessStatus:200,
};
app.use(cors(corsOptions));
app.use(express.json());
app.use(mongoSanitize({ replaceWith: "_" }));
app.use(BodyParser.json());
app.use(express.urlencoded({ extended: true }));
app.use(morgan("tiny"));
// app.use(cookieParser());
mongoose
.connect(db_url)
.then(() => {
console.log("database connection established successfully");
})
.catch((error) => console.log("this is the error", error));
const store = MongoDBStore.create({
mongoUrl: process.env.DB_URL,
ttl: 24 * 60 * 60 * 365, // 1 year
autoRemove: 'native',
crypto: {
secret,
},
});
store.on('error', function(error) {
console.log('SESSION STORE ERROR:', error);
});
// Session Settings
const sessionOptions = {
name: "geopilot_session",
secret: secret,
store: store,
resave: false,
saveUninitialized: false,
cookie: {
samesite: false,
// httpOnly: true,
// secure: true,
expires: Date.now() + 1000 * 60 * 60 * 24 * 365,
maxAge: 1000 * 60 * 60 * 24 * 365,
},
};
// app.set('trust proxy', 1) // trust first proxy
// Session Setup
app.use(session(sessionOptions));
// Helmet Setup
// app.use(helmet())
// Passport Middleware
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
// Routes
app.use("/api/country/:countryId/reviews", reviewRoute);
app.use("/api/country", countryRoute);
app.use('/api/city', cityRoute);
app.use('/api/city/:cityId/reviews', cityReviewRoute);
app.use('/api/activity', activityRoute);
app.use('/api/restaurant', restaurantRoute);
app.use('/api/restaurant/:restaurantId/reviews', reviewRoute);
app.use('/api/landmark', landmarkRoute);
app.use('/api/landmark/:landmarkId/reviews', reviewRoute);
app.use('/api/food', foodRoute);
app.use('/api/dishes', dishesRoute);
app.use('/api/outdoor', outdoorsRoute);
app.use('/api/search', searchRoute);
app.use('/api/user', userAuthRoute);
app.use('/api/contact', contactRoute);
// ----------- Deployment -----------
__dirname = path.resolve();
if (process.env.NODE_ENV === "production") {
app.use(express.static(path.join(__dirname, "../client/build")));
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "../client", "build", "index.html"));
});
}
// ----------- Deployment -----------
app.get("*", () => {
res.send("PAGE NOT FOUND");
});
I tried requesting in postman which works.
I tried changing things in the package.json to help proxy or run server.
I tried to switch endpoints and change CORS policy but it won't work.
For anyone seeing this and is stuck, I changed http-common baseUrl to match my website url (not localhost) and then had to do "npm run build" to make it actually implement the changes.

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client(after redirect)

My express app:
const https = require("https");
const http = require("http");
const path = require("path");
const express = require("express");
const cookieParser = require("cookie-parser");
const dotenv = require("dotenv");
const cors = require("cors");
const helmet = require("helmet");
const logger = require("morgan");
const compression = require("compression");
const session = require("express-session");
const pgSession = require("connect-pg-simple")(session);
const api = require("../server/routes");
const pool = require("../config/database");
/** ====== DotENV configuration */
dotenv.config({ path: require("find-config")(".env") });
const configurations = {
production: { ssl: true, port: process.env.PORT, hostname: "" },
development: {
ssl: false,
port: process.env.PORT,
hostname: process.env.STAGE_HOSTNAME,
},
};
const environment = process.env.NODE_ENV || "development";
const config = configurations[environment];
const {
responseHandlerMiddleware,
sessionChecker,
} = require("../server/middlewares");
const app = express();
var credentials = {
// key: fs.readFileSync("/etc/letsencrypt/live/myresorts.in/privkey.pem"),
// cert: fs.readFileSync("/etc/letsencrypt/live/myresorts.in/fullchain.pem"),
};
let server = config.ssl
? https.createServer(credentials, app)
: http.createServer(app);
app.use(cors());
app.options("*", cors());
app.use(helmet());
app.use(compression());
app.use(logger("dev"));
app.use(express.json({ limit: "20mb" }));
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.set("trust proxy", 1);
app.use(
session({
store: new pgSession({
pool,
tableName: "session",
}),
name: "user_sid",
secret: process.env.SESSION_SECRET,
resave: true,
cookie: { secure: false, maxAge: 60000 * 5 },
saveUninitialized: true,
})
);
app.use((req, res, next) => {
if (req.session.user && req.cookies.user_sid) {
res.redirect("/dashboard");
}
return next();
});
app.get("/", sessionChecker, (req, res) => {
res.redirect("/login");
});
app.set("views", path.resolve(__dirname, "../views"));
app.set("view engine", "ejs");
app.use(express.static("../public"));
app.use("/", sessionChecker, api);
// app.use(responseHandlerMiddleware);
app.route("/login").get(sessionChecker, (req, res) => {
res.render("pages/login");
});
app.route("/register").get(sessionChecker, (req, res) => {
res.render("pages/register");
});
app.route("/dashboard").get(sessionChecker, (req, res) => {
if (req.session.user && req.cookies.user_sid) {
res.render("pages/dashboard");
} else {
res.redirect("/login");
}
});
module.exports = server;
Session handler middleware:
const sessionChecker = (req, res, next) => {
console.log("sesson ---------", req.session);
console.log("cookies ---------", JSON.stringify(req.cookies));
if (req.session.user && req.cookies.user_sid) {
res.redirect("/dashboard");
} else {
return next();
}
};
Every time I trying to to do a signup, this error keeps popping up multiple times:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:372:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (/home/deep/Node_API/node_modules/express/lib/response.js:794:10)
at ServerResponse.location (/home/deep/Node_API/node_modules/express/lib/response.js:915:15)
at ServerResponse.redirect (/home/deep/Node_API/node_modules/express/lib/response.js:953:18)
at sessionChecker (/home/deep/Node_API/server/middlewares/session.middleware.js:5:13)
at Layer.handle [as handle_request] (/home/deep/Node_API/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (/home/deep/Node_API/node_modules/express/lib/router/index.js:328:13)
at /home/deep/Node_API/node_modules/express/lib/router/index.js:286:9
at Function.process_params (/home/deep/Node_API/node_modules/express/lib/router/index.js:346:12)
I'm trying to make a session management app using express-session. It should be able to preserve the session and if the session exists it should redirect to the dashboard else should redirect to the login page.
UPDATE:
As robert suggested in the comments, the issue was here:
app.use((req, res, next) => {
if (req.session.user && req.cookies.user_sid) {
res.redirect("/dashboard");
}
return next();
});
I changed it to:
else {
return next();
}
The error is fixed, but it is still not getting redirected to the dashboard after registering a user. Instead, gets called in the following manner multiple times:
GET /dashboard 302 246.426 ms - 64
GET /dashboard 302 247.005 ms - 64
GET /dashboard 302 245.620 ms - 64
GET /dashboard 302 247.940 ms - 64
GET /dashboard 302 246.114 ms - 64
I checked that this doesn't even get called:
app.route("/dashboard").get(sessionChecker, (req, res) => {
if (req.session.user && req.cookies.user_sid) {
res.render("pages/dashboard");
} else {
res.redirect("/login");
}
});
My registerUser controller:
const registerUser = catchAsync(async (req, res, next) => {
const { email, password } = req.body;
const user = await pool.query("select * from find_user_by_email($1)", [
email,
]);
if (user.rowCount) {
res.error = "User already exists";
return next(500);
}
const saltHash = generatePassword(password);
const response = await pool.query("select * from create_user($1, $2, $3)", [
email,
saltHash.salt,
saltHash.hash,
]);
if (response.rowCount) {
req.session.user = { email: response.rows[0].create_user };
res.redirect("/dashboard");
} else {
res.redirect("/register");
}
});
UPDATE(FIX):
It turns out redirects shouldn't be used when we are rendering template files. I replaced all res.redirect with res.render.

cookie isn't receive in browser in production environment but dev environment it 's ok

I deployed my react app on netlify which has backend deployed in heroku. When user login successsfully, they get refreshtoken is cookie in browser. It works fine on local host but after deploying in netlify, my browser doesn't receive cookies.
this is my code
const express = require("express");
const bodyParser = require("express");
const cookieParser = require("cookie-parser");
const cors = require("cors");
const dotenv = require("dotenv");
const route = require("./src/routers/index.js");
const mongoose = require("mongoose");
const app = express();
const URL = process.env.PORT || 5000;
dotenv.config();
app.use(cookieParser());
app.use(
cors({
origin:
"https://62ef3c87e9b20f3f533aad05--bucolic-vacherin-bcde68.netlify.app",
credentials: true,
})
);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
route(app);
mongoose.connect(process.env.URL_MONGOOESE).then(() => {
console.log("connect to Db successfully");
app.listen(URL, () => {
console.log(`listening on ${URL}`);
});
});
This is My backend controller
exports.Login = async (req, res) => {
userInfo = req.body.username;
try {
const findUser = await Authen.find({
username: req.body.username,
});
if (!findUser) {
res.status(404).json({ status: "not found account" });
} else {
var token = jwt.sign({ id: userInfo }, process.env.JWT_ACCESS_KEY, {
expiresIn: "5h",
});
var refreshToken = jwt.sign(
{ id: userInfo },
process.env.JWT_REFRESH_KEY,
{ expiresIn: "10h" }
);
refreshTokens.push(refreshToken);
res.cookie("refreshToken", refreshToken, {
httpOnly: false,
secure: true,
sameSite: "none",
expires: new Date(Date.now() + 60000 * 60 * 10),
});
res.status(200).json({
person_id: findUser[0].person_id,
token: token,
status: "ok",
refreshToken: refreshToken,
});
}
} catch (error) {
res.status(500).json({ status: error });
}
};

SessionID is generating a unique value on every post request

I am trying to identify the user that is on my application via sessionId, not actual info on the user account itself. However, what I am noticing is that the sessionId changes everytime the user performs an action on the page. As shown below. My goal would be to have the same sessionID from the point they open the webpage until they close it.
const app = require('express')();
const https = require('https');
const fs = require('fs');
const session = require('express-session');
function getDateTimestamp(){
var today = new Date();
var date = today.getFullYear()+'_'+(today.getMonth()+1)+'_'+today.getDate();
return date;
}
app.use(session({
resave: false,
saveUninitialized: true,
secret: 'whatever',
cookie: {
maxAge: 60*60*1000,
sameSite: true
}
}))
app.get('/', (req, res) => {
res.writeHead(200, {'Content-Type': 'text/html'});
var readStream = fs.createReadStream('index.html','utf8');
readStream.pipe(res);
});
app.post('/:fieldName/:flag/:time/:dashboard/:identifier/:user', (req, res) => {
console.log('POST message received', req.params);
if (req.params && req.params.fieldName) {
fs.appendFileSync(`./changeLog_${getDateTimestamp()}.csv`, `${req.params.fieldName},${req.params.flag},${req.params.time},${req.params.dashboard},${req.params.identifier},${req.params.user},${req.sessionID}\n`);
return res.send('OK')
}
res.status(400).end()
});
Client Side
function onParameterChange (parameterChangeEvent) {
parameterChangeEvent.getParameterAsync().then(function (param) {
parameterIndicator = 'Parameter'
const details = {
method: 'POST',
credentials: 'include'
//body: JSON.stringify(data),
// headers: {
// 'Content-Type': 'application/json'
// }
};
fetch(`url/${param.name}/${parameterIndicator}/${getDateTimestamp()}/${dashboardName}/${param.name}/${worksheetData}`, details).then((res) => {console.log(res);});
});
}
Here is my output showing a different session for the same user.
Just to illustrate my comment above, I actually have ran a quick test with a simple setup, and toggling saveUninitialized actually seems to make the difference:
// app.js
const express = require('express')
const app = express()
const session = require('express-session')
// Run the file as "node app false" or "node app true" to toggle saveUninitialized.
const saveUninitialized = process.argv[2] == "true" ? true : false
app.use(session({
resave: false,
saveUninitialized,
secret: 'whatever',
cookie: {
maxAge: 60 * 60 * 1000,
sameSite: true
}
}))
app.get("/", (req, res) => {
res.status(200).send(req.sessionID)
})
app.listen(3000, () => {
console.log('server started on http://localhost:3000')
})
// Response body
node app false
// 1st request: OTnFJD-r1MdiEc_8KNwzNES84Z0z1kp2
// 2nd request: 5UVVGng_G72Vmb5qvTdglCn9o9A4N-F6
// 3rd request: 9aGsAwnHh1p1sgINa1fMBXl-oRKcaQjM
node app true
// 1st request: StUrtHOKBFLSvl5qoFai6OQCm7TY87U-
// 2nd request: StUrtHOKBFLSvl5qoFai6OQCm7TY87U-
// 3rd request: StUrtHOKBFLSvl5qoFai6OQCm7TY87U-
But maybe there is more to it than that with your setup.

Resources