Search code examples
postgresqlexpresscookiesaxiosexpress-session

express-session setting new session every time, and does not persist after creating the session


  • I am using express for my backend on localhost:8080
  • Using react for my frontend on localhost:3000
  • No proxy is in use. Just simple http://localhost:3000/roster sending a request to http://localhost:8080/
  • I have set up all the cross origin and header stuff for cors() too
  • my postgres store is setup, and when I query the sessions table, I can see the session data there.
  • using axios for fetch on my front end, and I have configured credentials:true there also.

I am console logging my sessionID in the middleware (before its been set) and then I log it again in the request I am using to test my sessions. In the middleware it's unidentified as expected, and then once I log it in the request, I get a real session id as expected. Everything seems to work, but when I send the request for a second time, it send back a whole new sessionID instead of persisting.

As I looked further it said it could have something to do with the cookie not being set? But I have looks all over for configuring the setting correctly like resave and httpOnly, and all those seem to be okay too.

What am I doing wrong, how can I fix this? I have included my package.json for client and server here first....

PACKAGE.JSON CLIENT

{
  "name": "client.react",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "axios": "^0.21.1",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-router-dom": "^5.2.0",
    "react-scripts": "0.9.5"
  },
  "devDependencies": {},
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

PACKAGE.JSON SERVER

{
  "name": "backend",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bcryptjs": "^2.4.3",
    "body-parser": "^1.19.0",
    "connect-pg-simple": "^6.2.1",
    "cors": "^2.8.5",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "express-session": "^1.17.1",
    "jsonwebtoken": "^8.5.1",
    "pg": "^8.5.1",
    "uuid": "^8.3.2"
  }
}

CLIENT

/**
 * File: /src/pages/roster.js
 * Date: 01-28-2021
 * Author: jreyes
 * 
 * Date         |       Author      |           Change
 * ---------------------------------------------------------------------------------------
 * 01-29-2021   |       jreyes      |   initialization
 * ---------------------------------------------------------------------------------------
 */
import React from 'react';
import Axios from 'axios';

const Roster = () => {
    const [roster, setRoster] = React.useState([]);

    /** Fetch the roster. */
    const handleRoster = () => {
        Axios.get("http://localhost:8080/", {
            headers: {
                "Content-Type":"application/json"
            }
        }, { withCredentials: true })
        .then((response) => {
            console.log(response.data);
        })
    }

    return (
        <React.Fragment>
            <h1>Roster</h1>
            <button onClick={handleRoster}>Get Roster</button>
        </React.Fragment>
    )
}

export default Roster;

CLIENT CONSOLE LOG FROM CHROME

Notice the two different sessionIDs sent back from the server. From the same roster page and I just click the button once, and then a second time.

{hit: "!/", session: "SessionID: db9af88c-0101-4bf5-82c7-f57fbe9dac1d"}
hit: "!/"
session: "SessionID: db9af88c-0101-4bf5-82c7-f57fbe9dac1d"
__proto__: Object

roster.js:25 

{hit: "!/", session: "SessionID: b1a5ffd2-c986-4932-827c-a6ce644a0b3e"}
hit: "!/"
session: "SessionID: b1a5ffd2-c986-4932-827c-a6ce644a0b3e"
__proto__: Object

SERVER

/**
 * File: index.js
 * Date: 01-20-2021
 * Author: Bennm23
 * 
 * Date         |       Author      |           Change
 * ---------------------------------------------------------------------------------------
 * 01-20-2021   |       benm23      |   initialization
 * ---------------------------------------------------------------------------------------
 * 01-29-2021   |       jreyes      |   formatted code; fixed cors; added env 
 *              |                   |   functionality; db is outsourced in db.js;
 * ---------------------------------------------------------------------------------------
 * 01-30-2021   |       jreyes      |   added express sessions; uuid for unique strings;
 *              |                   |   added request to fetch user profile.
 * ---------------------------------------------------------------------------------------
 */

require('dotenv').config();
const express = require("express");
const app = express();
const db = require('./db');
const bcrypt = require("bcryptjs");
const cors = require("cors");
const {v4: uuidv4} = require('uuid');
const session = require('express-session');
const pgSession = require('connect-pg-simple')(session);

app.use(express.json());
app.use(express.urlencoded());
app.use(session({ 
    genid: (req) => {
        console.log("Inside middleware, not set yet: ");
        console.log(req.sessionID);
        return uuidv4();
    },
    store: new pgSession({
        pool: db,
        tableName: "session"
    }),
    secret: process.env.ES_SECRET,
    cookie:{
        maxAge:36000,
        httpOnly: false,
        secure: false
    },
    resave: false,
    saveUninitialized: true
}));

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

/** Set proper headers */
app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Credentials", true);
    res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE");
    res.header("Access-Control-Allow-Origin", "http://localhost:3000");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
    next();
});


/**
 * Test request for sessions.
 */
app.get("/", (req, res) => {
    console.log('Hit detected: defualt home route');
    console.log('Session detected: ' + req.sessionID);
    res.json({
        "hit":"!/",
        "session": "SessionID: " + req.sessionID
    });
});

/** 
 * Register a new user.
 *
 * Errors:
 * !email: email has been taken
 * !register: database malfunction 
 * !hashing: hashing malfunction
 *
 * */
app.post("/signup", async (req, res) => {
    const userExistsReq = "SELECT * FROM  users WHERE email = $1";
    const userExistsRes = await db.query(userExistsReq,[req.body.email]);
    
    // Email already exists in the database.
    if(userExistsRes.rowCount > 0){
        res.status(200);
        res.json({"error":"!email"});
    }
    else{

        try {
            // Hash password.
            const salt = await bcrypt.genSalt();
            const hashedPassword = await bcrypt.hash(req.body.password, salt)

            const registerTemplate = "INSERT INTO users (email, password, firstname, lastname) VALUES ($1,$2,$3,$4)";

            // Add user to database.
            try {
                const registerRes = await db.query(registerTemplate, 
                    [
                        req.body.email, 
                        hashedPassword, 
                        req.body.firstname, 
                        req.body.lastname
                    ]
                );
                res.status(201);
                res.json({"good":"register"});

            // Error adding user.
            } catch (err) {
                res.status(500);
                console.error("error: " + err);
                res.json({"error":"!register"});
            }
        }
        // Error hashing password.
        catch {
            res.status(500);
            console.error("error: " + err)
            res.json({"error":"!hashing"});
        }
    }
});

/**
 * Login an existing user.
 *
 * Errors:
 *
 * !email: user does not exist
 * !password: user entered wrong password
 * !login: database malfunction.
 */
app.post("/login", async (req, res) => {
    // Verify user presence in db.
    const userExistsReq = "SELECT * FROM users WHERE email = $1";
    const userExistsRes = await db.query(userExistsReq, [req.body.email]);

    // User does not exits.
    if(userExistsRes.rowCount == 0){
        res.status(200);
        res.json({"error":"!email"});
    }
    else{

        // Test user credentials.
        try {
            if(await bcrypt.compare(req.body.password, userExistsRes.rows[0].password)){
                const email = userExistsRes.rows[0].email;
                const firstname = userExistsRes.rows[0].firstname;
                 
                res.status(200); 
                res.json({"good":"login"})
            }else{
                res.status(200);
                res.json({"error":"!password"})
            }
        
        // Error finding user.
        } catch (err) {
            res.status(200);
            console.error("Error while running: " + err);
            res.json({"error":"!login"});
        }
    }
});

/**
 * Fetch the roster of players.
 * 
 * !roster: database malfunction
 */
app.get("/roster", async (req, res) => {
    const fetchRosterTemplate = "SELECT * FROM users";
    const response = await db.query(fetchRosterTemplate);

    if (response.rowCount == 0) {
        res.status(200);
        res.json({"error":"!roster"});
    } else {
        res.status(200);
        res.json(response.rows);
    }
});

/**
 * Start server.
 */
app.set("port", 8080);
app.listen(app.get("port"), () => {
    console.log(`Find the server at http://localhost:${ app.get("port") }`);
});

SERVER CONSOLE LOG

This is the console after two requests from the roster page in my client. I click the button twice and these are the two things that are logged.

jreyes@x1carbon:~/Projects/mothers-rfc/server$ node index.js 
body-parser deprecated undefined extended: provide extended option index.js:29:17
Find the server at http://localhost:8080

Inside middleware, not set yet:
undefined
Hit detected: default home route
Session detected: db9af88c-0101-4bf5-82c7-f57fbe9dac1d

Inside middleware, not set yet:
undefined
Hit detected: default home route
Session detected: b1a5ffd2-c986-4932-827c-a6ce644a0b3e

Solution

  • Setting httpOnly solved my issue. I had it set to false and it needs to be true. I left the secure option for cookies set to false.

    httpOnly: true
    

    solved my problem :)