Express-Session cookie updates not being persisted

25 Views Asked by At

I have an ExpressJS API and NextJS UI. I'm trying to generate session tokens. I'm able to generate the token and I see it saved on my MongoDB session storage, but when I try to re-use that session token at on another API endpoint, I get back a 401.

I'm aware of previous questions on the subject and have followed all advice given, and I'm still receiving the same issue. I don't know what else I'm missing.

I'm using containerized node 18.13.0 and the default latest image of mongo.

app.js

import express from 'express';
import bodyParser from 'body-parser';
import cors from 'cors';
import session from 'express-session';
import helmet from 'helmet';
import MongoDBStore from 'connect-mongodb-session';

import router from './routes/index.js';

app.use(cors({
    origin: [
        'http://localhost:8080',
        'http://127.0.0.1:8080',
        'http://127.0.0.1:3000',
        'http://localhost:3000',
    ],
    methods: ['POST', 'PUT', 'GET', 'DELETE'],
    credentials: true,
}));

app.use(session({
    secret: env.SESSION_SECRET,
    cookie: {
        maxAge: 3600000, // 1hr
        httpOnly: true,
        rolling: true,
        secure: false,
    },
    store,
    resave: false,
    saveUninitialized: false,
}));

app.use(router);

./routes/index.js

import Auth from './auth.js';
import crud from './crud.js';
const router = Router();

router.post('/signup', Auth.signup);
router.post('/login', Auth.login);
router.get('/data', Auth.authMiddleware(['admin', 'basic', 'premium', 'standard']), crud.getData);
export default router;

./routes/auth.js

const authMiddleware = (permittedRoles) => async (req, res, next) => {
    try {
        if (permittedRoles.indexOf(req.session.user.role) > -1) {
            next();
        } else {
            await req.session.destroy();
            await res.clearCookie('connect.sid');
            res.status(401).json('forbidden');
        }
    } catch (err) {
        await req.session.destroy();
        await res.clearCookie('connect.sid');
        await res.status(401).json('Session Expired. Log in again.');
    }
};

const login = async (req, res) => {
    try {
        const maybeUser = await UserController.getUser(req.body.email);
        if (maybeUser && await bcrypt.compare(req.body.password, maybeUser.password)) {
            req.session.user = { email: maybeUser.email, role: maybeUser.role };
            req.session.save(); // when I console.log, it works, you see the additional email and role attributes in .user property
            res.status(201).json({ success: `Welcome, ${req.body.email}`});
        } else {
            res.status(400).json('Invalid Email or Password');
        }
    } catch (err) {
        res.status(500).json('Something went wrong on our side. Contact an administrator.');
    }
};

const signup = async (req, res) => {
    try {
        const { email } = req.body;
        const password = await bcrypt.hash(req.body.password, 15).then((hash) => hash);
        const resp = await UserController.addUser({ email, password });
        if (resp.success && !resp.error) {
            res.status(201).json({ success: 'User Created.' });
        } else {
            res.status(400).json('Unable to create account.');
        }
    } catch (err) {
        // res.status(500).json(err);
        res.status(500).json('Something went wrong on our side. Contact an administrator.');
    }
};

./routes/crud.js

const getData = (req, res) => {
    console.log(req.session); // session should include email and role properties but doesn't
    ....
}

In my nextJS UI, I make the API call like this:

const response = await fetch('localhost:8080/signup', {
            method: 'POST',
            body: await statesToFormData(),
        });

...

const response = await fetch('localhost:8080/login', {
            method: 'POST',
            body: await statesToFormData(),
        });

const response = await fetch('localhost:8080/data', {
            method: 'GET',
            credentials: 'include',
        });

Edit: I solved my problem. req.session(...) takes in a callback, so my login endpoint now looks like this:

const login = async (req, res) => {
    ...
    if (...) {
     req.session.user = { email: maybeUser.email, role: maybeUser.role };
     req.session.save((err) => {
                if (err) {
                   res.status(500).json(...);
                } else {
                    res.status(201).json(...);
                }
            });
    } else { ... }
    ...
};
0

There are 0 best solutions below