I've seen this question be asked many times, but there is still no answer after I've gone through a dozen similar questions. The core of the problem is I have a node server with a mongodb database, and a React frontend. I want to store user sessions and get the session from the client when they hit the endpoint.
I've been using express-session, I also tried client-sessions, still no use. When the user logs in, I store their user id in the session object. However when I try to access that session object in a different route, it is undefined.
import express, { json, urlencoded } from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import connectToDB from './utils/database.js';
import MongoStore from 'connect-mongo';
import session from 'express-session';
import authRouter from './routes/auth.routes.js';
import attendanceRouter from './routes/attendance.routes.js';
import userRouter from './routes/user.routes.js';
dotenv.config();
const app = express();
app.disable('x-powered-by');
app.use(express.static('public'));
app.use(cors({
origin: 'http://localhost:3000', // Frontend URL
credentials: true,
}));
connectToDB();
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
store: MongoStore.create({ mongoUrl: process.env.MONGO_URI, collectionName: 'sessions' }),
cookie: {
maxAge: 1000 * 60 * 60 * 24 // 1 day
},
}));
app.use(json());
app.use(urlencoded({ extended: true }));
app.use('/', authRouter);
app.use('/attendance', attendanceRouter);
app.use('/user', userRouter);
app.listen(process.env.PORT || 5000, () => {
console.log('Server is running on port 3000');
})
the authRoutes
has a authController
that handles the login, register. The authController
looks like this,
import { User } from "../models/user.model.js";
import bcrypt from 'bcryptjs';
const registerPost = async (req, res) => {
const { username, password, isGuard, phoneNumber, lastSubmitted } = req.body;
const hashedPassword = bcrypt.hashSync(password, 10);
const alreadyExistingUser = await User.findOne({ username });
if (alreadyExistingUser) {
console.log("User already exists");
return res.status(400).json({
message: "User already exists"
});
}
const user = new User({
username,
password: hashedPassword,
isGuard,
phoneNumber,
lastSubmitted
});
console.log("Registration end point hit");
try {
await user.save();
req.session.userid = user._id;
res.status(201).send('User created successfully');
} catch (error) {
res.status(400).send(error.message);
}
}
const loginPost = async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user) {
console.log("User not found");
return res.status(404).send('User not found');
}
const isMatch = bcrypt.compareSync(password, user.password);
if (!isMatch) {
console.log("Invalid password");
res.status(401).send('Invalid password');
}
req.session.userid = user._id;
res.status(200).send('Logged in successfully');
}
const isSignedIn = async (req, res) => {
if (req.session.userid) { // this is always undefined even after logging in
const user = await User.findById(req.session.userid);
return res.status(200).json({
username: user.username,
isGuard: user.isGuard,
phoneNumber: user.phoneNumber
});
} else {
return res.status(401).send('You are not logged in');
}
}
export default {
registerPost,
loginPost,
isSignedIn
}
I've gone through docs, videos and stackoverflow questions and still no answer. What am I missing?
My front-end code looks like this,
import React, { useEffect, useState } from 'react';
import Cookies from 'js-cookie';
import axios from 'axios';
const Dashboard = () => {
// const [cookie, setCookie] = useState('');
// useEffect(async () => {
// const cookieValue = await Cookies.get('userid');
// console.log(cookieValue)
// console.log(cookieValue.userid)
// setCookie(cookieValue);
// }, [])
const handleClick = async () => {
const response = await axios.get('http://localhost:5000/issigned');
console.log(response);
}
return (
<div>
<h1 onClick={handleClick}>Dashboard</h1>
</div>
);
}
export default Dashboard;
My package.json
looks like this
// server side
{
"name": "ideaboard",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"dev": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"bcryptjs": "^2.4.3",
"client-sessions": "^0.8.0",
"connect-mongo": "^5.1.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-session": "^1.18.0",
"helmet": "^7.1.0",
"moment": "^2.30.1",
"mongoose": "^8.5.2",
"multer": "^1.4.5-lts.1"
},
"devDependencies": {
"nodemon": "^3.1.4"
}
}
// client side
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.7.3",
"js-cookie": "^3.0.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Please help me.
Cookie is a secured information, therefore client credentials must be passed along with the requests. In a same-site request, credentials are automatically included in every request, and cookie will be circulated successfully.
By default, a cross-origin CORS request is made without credentials. So, no cookies, no client certs, no automatic Authorization header, and Set-Cookie on the response is ignored. However, same-origin requests include credentials.
If the login in this case is a cross-origin CORS request and if it did NOT have credentials explicitly set, though the server would have set the header Set-Cookie, the client would have ignored it. This could be the reason for the failure. And the solution as you can see, to include credentials.
example:
fetch(url, {
credentials: "include",
});
This post - How to win at CORS, talks about this in detail.