You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PERN栈中Passport登录后/current-session无法识别已认证会话问题

问题排查:PERN栈会话认证失效

前端POST /login提交账号密码可成功登录,此时req.isAuthenticated()返回true;但后续GET /current-session时,req.isAuthenticated()返回false。数据库session表已生成会话数据,/login响应头也返回了HttpOnly的connect.sid cookie,但/current-session请求中浏览器未携带该cookie,且passport.deserializeUser()从未被调用。

数据库会话表数据

sidsessexpire
T6akfD8h0TWysQD2jZ5LqIBlxCBIcLSp[object Object]2024-03-06 00:54:22 +0000

/login响应头中的Cookie

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

后端代码

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;

前端Fetch请求

登录请求

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,
    })

会话校验请求

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

排查建议

  • 修复deserializeUser日志错误:原代码中passport.deserializeUser的日志写的是serializeUser invoked,先修正这个,确保能准确看到该函数是否被调用。
  • 配置Cookie的SameSite属性:跨域场景下,显式设置session的cookie: { sameSite: 'none', secure: true }(HTTPS环境),本地开发可暂时用sameSite: 'lax',避免浏览器默认限制Cookie发送。
  • 清理前端请求的冗余头:移除前端fetch请求中的Access-Control-Allow-Origin头,该头由服务器返回,前端设置会导致冲突。
  • 优化序列化逻辑:serializeUser建议只存储用户ID而非整个对象,减少序列化体积并避免潜在问题,同时在deserializeUser中通过ID查询完整用户信息:
    passport.serializeUser((user, done) => {
      console.log("serializeUser invoked");
      done(null, user.user_id);
    });
    passport.deserializeUser((userId, done) => {
      console.log("deserializeUser invoked");
      pool.query("SELECT * FROM users WHERE user_id = $1", [userId], (err, result) => {
        if (err) return done(err);
        done(null, result.rows[0]);
      });
    });
    
  • 验证域名一致性:确认前端URL指向的后端地址与CORS配置的origin完全一致,包括协议、域名、端口,无拼写错误。
  • 检查浏览器Cookie状态:在浏览器开发者工具的Application面板,查看connect.sid的Domain、Path、SameSite等属性是否符合预期,若Domain设置错误,浏览器不会携带Cookie。

内容的提问来源于stack exchange,提问作者James

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 12:34:59