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()从未被调用。
数据库会话表数据
| sid | sess | expire |
|---|---|---|
| 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
相关产品推荐
相关产品推荐

