Why sessionId get changed in every request?

I am developing a student request management system. I have used node, mysql and express. When a user logged-in he should be redirected to a dashboard.

I am using express-session to manage user sessions.

When a user successfully logged-in, the browser will send a GET request to the dashboard. But before handling the GET request there is a middleware called authChecker to check the session created on a successful login. Although the req.session.user is set inside the /login route, req.session.user is undefined inside the authChecker. Therefore user gets redirected back to login page even he logged-in correctly.

When debugging this issue in VS Code, I found that the req.session.id is changed from /login to authChecker. I think that means req.session object is changed. That’s why req.session.user is undefined inside the authChecker.

OS: Linux Mint 20 Cinnamon
Node version: 14.15.0
npm version: 6.14.9
Browser: Firefox 86.0.1 (64 bit) and Google Chrome 89.0.4389.90 (64 bit)
(Both Firefox and Chrome shows the same error)

N.B: Several friends of mine on Windows and Macs have tried this app on their machines without any problem. So could be this is because of some setting of my computer ?

Server.js

import cookieParser from "cookie-parser";
import express from "express";
import expressMySqlSession from "express-mysql-session";
import expressSession from "express-session";
import createError from "http-errors";
import logger from "morgan";
import mysql from "mysql";
import path from "path";
import socketio from "socket.io";
import {
    MYSQL_DATABASE,
    MYSQL_HOST,
    MYSQL_PASSWORD,
    MYSQL_USER,
    NODE_ENV,
    PORT,
    SESS_LIFETIME,
    SESS_NAME,
    SESS_SECRET,
} from "./config/constants";
import router from "./routes/index";

// MySQL database
const MySQLOptions = {
    host: MYSQL_HOST,
    user: MYSQL_USER,
    password: MYSQL_PASSWORD,
    database: MYSQL_DATABASE,
};
const pool = mysql.createPool({ connectionLimit: 10, ...MySQLOptions });
pool.getConnection((err, conn) => {
    if (err) throw err;

    // Check connection
    console.log("Connected to MySQL database");
    conn.release();
});

// Express app
const app = express();

// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "ejs");

// Options
app.disable("x-powered-by");
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(express.static("public"));
app.use(logger("dev"));

// Session store
const MySQLStore = expressMySqlSession(expressSession);
const sessionStore = new MySQLStore(
    { clearExpired: true, checkExpirationInterval: 600000, ...MySQLOptions },
    pool
);
const session = expressSession({
    name: SESS_NAME,
    secret: SESS_SECRET,
    saveUninitialized: false,
    resave: false,
    store: sessionStore,
    cookie: {
        sameSite: true,
        secure: NODE_ENV === "production",
        maxAge: parseInt(SESS_LIFETIME),
    },
});
app.use(session);

// Check session
app.use((req, res, next) => {
    if (!req.session.user) {
        res.clearCookie(SESS_NAME);
    }
    next();
});

// Start server
const io = socketio(
    app.listen(PORT, () =>
        console.log(`App running at http://localhost:${PORT}`)
    )
);

io.use((socket, next) => {
    let req = socket.handshake;
    let res = socket.request.res || {};
    cookieParser()(req, res, (err) => {
        if (err) return next(err);
        session(req, res, next);
    });
});

// Routing
const {
    loginRouter,
    logoutRouter,
    dashboardRouter,
    requestRouter,
    replyRouter,
} = router(pool, io);

app.use("/login", loginRouter);
app.use("/logout", logoutRouter);
app.use("/dashboard", dashboardRouter);
app.use("/request", requestRouter);
app.use("/reply", replyRouter);
app.use("*", (req, res) => {
    // Default route
    res.redirect("/login");
});

// catch 404 and forward to error handler
app.use(function (req, res, next) {
    next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
    // set locals, only providing error in development
    res.locals.message = err.message;
    res.locals.error = req.app.get("env") === "development" ? err : {};

    // render the error page
    res.status(err.status || 500);
    res.render("error");
});

login.js

import express from "express";
import { sessionChecker } from "../util/middleware";
import { sessionizeUser } from "../util/helpers";
import { getUserById } from "../util/database";

const handler = (pool) => {
    const loginRouter = express.Router();

    // Get request for login page
    loginRouter.get("", sessionChecker, (req, res) => {
        // Show login page
        res.render("login");
    });

    // Post request for login
    loginRouter.post("", sessionChecker, async (req, res) => {
        const { userId, password } = req.body;

        const user = await getUserById(pool, userId);

        if (!user) {
            res.redirect("/login");
        } else if (!user.validPassword(password)) {
            res.redirect("/login");
        } else {
            req.session.user = sessionizeUser(user);
            res.redirect("/dashboard");
        }
    });

    return loginRouter;
};

export default handler;

authChecker & sessionChecker

// Used for login route
    const sessionChecker = (req, res, next) => {
    if (req.session.user) {
        res.redirect("/dashboard");
    } else {
        next();
    }
};

// If session unavailable, redirect to login
// Used for all protected routes
    const authChecker = (req, res, next) => {
    if (req.session.user) {
        next();
    } else {
        res.redirect("/login");
    }
};

}))
});

dashboard.js

dashboardRouter.get("", authChecker, async (req, res) => {
//code
}

Github repo: GitHub - nimanthadilz/srms
(Use the develop branch for debugging)

Works on my machine:
Ubuntu 20.04.2 LTS
Node: 14.11.0
npm: 6.14.8

In the future you should create at least a basic README and sample .env file (and ideally a db migration script) so the people who are willing to help won’t have to spend too much time on figuring out the setup.

1 Like

Sorry about the inconvenience. Then There is a problem with my machine. What could go wrong ? Thank you for your valuable time.

Inside the login route handler req.session.user is available.

But inside the authChecker middleware it is not there.

Even the session.id s are different.

What could be the reason ?

I think I managed to reproduce the error - looks like only the first user that logged in is able to restore its session. That’s why it works for your friends - they have an empty sessions table.
Unfortunately I’m not familiar with the packages you’re using to handle sessions, so maybe someone else will have an idea.

1 Like

I also noticed something. Even a user has a session in the database a new session entry will be created without using the previously stored session.
I think the problem is with the sessionStore. I just used the default memoryStore for the store. Then the app worked just fine.