Search code examples
node.jsexpressauthenticationpassport.jsexpress-session

Passport - req.isAuthenticated() returns true in /login route after local strategy, but not in /current-session after passport.authenticate("session")


I am building a full-stack app with a PERN stack. I can POST a username and password from the front-end to /login and log in successfully - isAuthenticated() returns true. However, if I then send a GET request to /current-session isAuthenticated() returns false.

I am wondering where things might be going wrong. I have looked at every related issue I can find on Stack Overflow, and haven't found an answer yet.

The server is running. The database connection is established, and queries to the database work. The database table "session" is being populated. For example:

sid sess expire
T6akfD8h0TWysQD2jZ5LqIBlxCBIcLSp [object Object] 2024-03-06 00:54:22 +0000

In the browswer, I can see the sid as a HTTP only cookie from the response headers when I POST to /login. For example:

connect.sid=s%3AT6akfD8h0TWysQD2jZ5LqIBlxCBIcLSp.H01oGHOfFy9m6Tt451i%2F0vpoKuYEDPN8insL7Wx8GaU;
Path=/;
Expires=Wed, 06 Mar 2024 00:54:21 GMT;
HttpOnly

However, when I GET to /current-session, I don't see the cookie in the browser response.

It seems as if app.use(passport.session()); is for some reason telling app.get("/current-session"... that there is no current user session/current authenticated user. Wondering whether the session is not being maintained for some reason...?

From my logs, I can see that passport.deserializeUser() is never called.

Here is the current code:

const express = require("express");
const session = require("express-session");
const passport = require("passport");
const LocalStrategy = require("passport-local");
const bcrypt = require("bcrypt");
const cors = require("cors");
const cookieParser = require("cookie-parser");
const { Pool } = require("pg");
const pgSession = require("connect-pg-simple")(session);
require("dotenv").config();

// -------------  GENERAL SETUP  --------------- //
const app = express();
const PORT = process.env.PORT || 3001;
app.use(
  cors({
    origin: "https://nightlifeapp.onrender.com", // "http://localhost:5173",
    credentials: true,
    "Access-Control-Allow-Credentials": true,
  })
);
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());

// -----------  DATABASE CONNECTION  ----------- //
// Create a PostgreSQL connection pool
const pool = new Pool({
  connectionString: process.env.ELEPHANTSQL_CONNECTION_URL,
  max: 5,
});

// Test the database connection
pool.connect((err, client, done) => {
  if (err) {
    console.error("Error connecting to the database", err);
  } else {
    console.log("Connected to the database");
  }
});


// -----------  PASSPORT STRATEGIES  ----------- //
passport.use(
  "local",
  new LocalStrategy((username, password, done) => {
    // Query the PostgreSQL database to find a user by username
    pool.query(
      "SELECT * FROM users WHERE username = $1",
      [username],
      (err, result) => {
        if (err) {
          return done(err);
        }
        // Check if the user exists
        const user = result.rows[0];
        if (!user) {
          return done(null, false);
        }
        // Check if the password is correct
        if (!bcrypt.compareSync(password, user.password_hash)) {
          return done(null, false);
        }
        // If the username and password are correct, return the user
        return done(null, user);
      }
    );
  })
);

passport.serializeUser((user, done) => {
  console.log("serializeUser invoked");
  done(null, user);
});
passport.deserializeUser((user, done) => {
  console.log("serializeUser invoked");
  done(null, user);
});


// -------------  EXPRESS SESSION  ------------- //
app.use(
  session({
    store: new pgSession({
      pool,
      tableName: "session",
    }),
    secret: process.env.EXPRESS_SESSION_SECRET_KEY,
    resave: false,
    saveUninitialized: false,
    cookie: {
      maxAge: 14 * 24 * 60 * 60 * 1000, // 14 days session timeout
    },
  })
);

app.use(passport.initialize());
app.use(passport.session());


// -----------------  ROUTING  ----------------- //
app.get("/current-session", passport.authenticate("session"), (req, res) => {
  if (req.isAuthenticated()) {
    console.log("At GET /current-session... isAuthenticated() returned true");
  } else {
    console.log("At GET /current-session... isAuthenticated() returned false");
  }

  if (!req.user) {
    res.json({ currentlyLoggedIn: false });
  } else {
    res.json({
      currentlyLoggedIn: true,
      userId: req.user.user_id,
      username: req.user.username,
    });
  }
});

app.post(
  "/login",
  passport.authenticate("local"), (req, res) => {
    if (req.isAuthenticated()) {
      console.log("At POST /login... isAuthenticated() returned true");
    } else {
      console.log("At POST /login... isAuthenticated() returned false");
    }

    return res.json({
      loginSuccessful: true,
      userId: req.user.user_id,
      username: req.user.username,
    });
  }
);

// ------------------  SERVER  ----------------- //
const server = app.listen(PORT, () =>
  console.log(`Server is listening on port ${PORT}`)
);

server.keepAliveTimeout = 120 * 1000;
server.headersTimeout = 120 * 1000;

On the front-end, the fetch requests I'm making to /login and /current-session are as follows:

fetch(`${URL}/login`, {
      method: "POST",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
        "Access-Control-Allow-Origin": "https://nightlifeapp.onrender.com",
      },
      body: jsonData,
    })

And

fetch(`${URL}/current-session`, {
      method: "GET",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
        "Access-Control-Allow-Origin": "https://nightlifeapp.onrender.com",
      },
    })```


Any suggestions would be greatly appreciated!


Solution

  • So, it turns out that the problem here was that cookies were being blocked because the front-end and the back-end were hosted on different Render.com sub-domains, and so the cookies were not being used to keep the session going.

    I could have used different/custom domain names to avoid the cookies being blocked.

    Instead, the solution I came up with was hosting the front end static files on the same server as the Express API end-points. To do that, I placed the build/dist files for the React front-end in a folder on the Express server, and then used app.use(express.static("dist")); to serve the front-end index.html file - by default on calls to the '/' end-point.

    I used this explanation as a starting point: https://youtu.be/4pUBO31nkpk?si=M5GXQGCa__W0_yH9