javascriptreactjsnode.jsexpresssession

Express session variable is undefined on other routes after defining it in the login route


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.


Solution

  • 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.