Express中Passport.js Local策略序列化用户到会话失败排查
问题:Passport.js Local策略登录时出现「Failed to serialize user into session」错误
使用Express框架的Passport.js Local策略从MySQL数据库验证用户登录时,触发「Failed to serialize user into session」错误。参考同步数组存储用户的教程适配为MySQL异步获取数据,用.then处理Promise后仍报错。以下是错误栈、配置代码、路由逻辑及表结构,请求排查问题。
错误栈信息
Error: Failed to serialize user into session at pass (/NodeJS/Projects/user-management/node_modules/passport/lib/authenticator.js:296:19) at serialized (/NodeJS/Projects/user-management/node_modules/passport/lib/authenticator.js:301:7) at /NodeJS/Projects/user-management/src/services/passport-config.js:27:44 at pass (/NodeJS/Projects/user-management/node_modules/passport/lib/authenticator.js:309:9) at Authenticator.serializeUser (/NodeJS/Projects/user-management/node_modules/passport/lib/authenticator.js:314:5) at /NodeJS/Projects/user-management/node_modules/passport/lib/sessionmanager.js:33:10 at Immediate._onImmediate (/NodeJS/Projects/user-management/node_modules/express-session/session/store.js:54:5) at process.processImmediate (node:internal/timers:476:21)
相关代码
Passport配置代码
const LocalStrategy = require('passport-local').Strategy const bcrypt = require('bcrypt') function initPassport(passport, getUserByName, getUserById) { const authenticateUser = async (name, password, done) => { const user = getUserByName(name) if (user == null) { return done(null, false, { message : 'No user with that name'}) } try { if (await bcrypt.compare(password, user.password)) { return done(null, user) } else { return done(null, false, { message: 'Password incorrect'}) } } catch (e) { return done(e) } } passport.use(new LocalStrategy({ usernameField: 'name'}, authenticateUser)) passport.serializeUser((user, done) => done(null, user.id)) passport.deserializeUser((id, done) => { return done(null, getUserById(id)) }) } module.exports = initPassport //used in routes web-user-routes , TODO: move functions to own controller and services
路由逻辑代码
const express = require('express'); const router = express.Router(); const bcrypt = require('bcrypt'); const passport = require('passport'); const flash = require('express-flash'); const session = require('express-session'); const methodOverride = require('method-override'); let { singledbentry, refresh, refreshTwo } = require("../services/db.js") const controller = require('../controllers/web-user-controller'); const webUsers = refresh('SELECT useruniqueid, username, password, userrole, email FROM webusers') webUsers.then((users) => { const initPassport = require('../services/passport-config.js'); initPassport( passport, name => users.find(user => user.username === name), id => users.find(user => user.useruniqueid === id) ) }) router.use(flash()) router.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false })) router.use(passport.initialize()) router.use(passport.session()) router.use(methodOverride('_method')) router.get('/webusers', async (_req, res, next) => { let userData = refresh('SELECT useruniqueid, username, password, userrole, email FROM webusers') res.render('webusers', { data: await userData }); }) router.post('/webusers', async (req, res) => { let userUniqueId = Date.now().toString() let userName = req.body.username let hashedPassword = await bcrypt.hash(req.body.password, 10) let userRole = req.body.userrole let userEmail = req.body.email let sql = "insert into webusers values(?,?,?,?,?)"; let sqlValues = [userUniqueId, userName, hashedPassword, userRole, userEmail]; await refreshTwo(sql, sqlValues); let userData = refresh('SELECT useruniqueid, username, password, userrole, email FROM webusers') res.render('webusers', { data: await userData }); }) router.post('/webusers/delete', async (req, res) => { let deleteId = req.body.useruniqueid; let sql = "delete from webusers where useruniqueid ='" + deleteId + "';" await singledbentry(sql); let userData = refresh('SELECT useruniqueid, username, password, userrole, email FROM webusers') res.render('webusers', { data: await userData }); }) router.get('/login', checkNotAuthenticated, (req, res) => { res.render('login.ejs'); }) router.post('/login', checkNotAuthenticated, passport.authenticate('local', { successRedirect: '/', failureRedirect: '/login', failureFlash: true })) router.get('/register', checkNotAuthenticated, (req, res) => { res.render('register.ejs'); }) router.post('/register', checkNotAuthenticated, async (req, res) => { try { let userUniqueId = Date.now().toString() let userName = req.body.name let userRole = 'USER' let hashedPassword = await bcrypt.hash(req.body.password, 10) let userEmail = req.body.email let sql = "insert into webusers values(?,?,?,?,?)"; let sqlValues = [userUniqueId, userName, hashedPassword, userRole, userEmail]; await refreshTwo(sql, sqlValues); res.redirect('/login') } catch { res.redirect('/register') } }) router.delete('/logout', (req, res) => { req.logOut() res.redirect('/login') }) // Delete uses library method override and uses the following code frontend: // <form action="/logout?_method=DELETE" method="POST"> // <button type="submit">Log Out</button></form> function checkAuthenticated(req, res, next) { if (req.isAuthenticated()) { return next() } res.redirect('/login') } function checkNotAuthenticated(req, res, next) { if (req.isAuthenticated()) { return res.redirect('/') } next() } function isAdmin(req, res, next) { if (req.user.role === 'ADMIN') { return next(); } console.log('nope!') return res.redirect('/'); } module.exports = router;
MySQL表结构代码
// CREATE TABLES FOR <<<<<< WEB-USERS >>>>>>> IF NOT EXIST async function initWebUserTable() { await pool.query ('CREATE TABLE IF NOT EXISTS webusers (useruniqueid VARCHAR(45), username VARCHAR(45), password VARCHAR(99), userrole VARCHAR(45), email VARCHAR(45), PRIMARY KEY (useruniqueid))'); }
问题排查与解决方案
核心问题点
- 序列化字段名不匹配:Passport配置中
serializeUser尝试读取user.id,但数据库用户ID字段为useruniqueid,导致获取到undefined,触发序列化失败。 - Passport初始化时机错误:路由文件中通过
webUsers.then()异步初始化Passport,但Express的Passport中间件(passport.initialize()、passport.session())先于该回调执行,可能导致登录请求到来时Passport未完成初始化。 - 用户数据依赖静态数组:一次性拉取用户到内存数组,后续数据库更新(注册、删除)不会同步到数组,导致验证/反序列化时数据不一致。
- 权限中间件字段名错误:
isAdmin函数使用req.user.role,但数据库字段为userrole,导致权限判断失效。
修复步骤
1. 修正序列化字段名
修改Passport配置中的serializeUser,将user.id改为user.useruniqueid:
passport.serializeUser((user, done) => done(null, user.useruniqueid))
2. 调整Passport初始化时机
将数据库查询与Passport初始化移至主入口文件(如app.js),确保初始化完成后再挂载路由:
const express = require('express'); const app = express(); const passport = require('passport'); const { refresh } = require('./src/services/db.js'); const userRoutes = require('./src/routes/web-user-routes.js'); async function startServer() { // 先完成用户数据拉取与Passport初始化 const users = await refresh('SELECT useruniqueid, username, password, userrole, email FROM webusers'); const initPassport = require('./src/services/passport-config.js'); initPassport( passport, name => users.find(user => user.username === name), id => users.find(user => user.useruniqueid === id) ); // 配置全局中间件 app.use(express.urlencoded({ extended: false })); app.use(require('express-flash')()); app.use(require('express-session')({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false })); app.use(passport.initialize()); app.use(passport.session()); app.use(require('method-override')('_method')); // 挂载路由 app.use('/', userRoutes); app.listen(3000, () => console.log('Server started on port 3000')); } startServer();
同时移除路由文件中的Passport初始化与中间件配置,避免重复。
3. 改为实时查询数据库(推荐)
放弃静态数组方式,让getUserByName和getUserById直接查询数据库,保证数据实时性:
const LocalStrategy = require('passport-local').Strategy; const bcrypt = require('bcrypt'); const { refresh } = require('./db.js'); function initPassport(passport) { const authenticateUser = async (name, password, done) => { // 实时查询数据库获取用户 const users = await refresh('SELECT useruniqueid, username, password, userrole, email FROM webusers WHERE username = ?', [name]); const user = users[0]; if (!user) { return done(null, false, { message: 'No user with that name' }); } try { if (await bcrypt.compare(password, user.password)) { return done(null, user); } else { return done(null, false, { message: 'Password incorrect' }); } } catch (e) { return done(e); } }; passport.use(new LocalStrategy({ usernameField: 'name' }, authenticateUser)); passport.serializeUser((user, done) => done(null, user.useruniqueid)); passport.deserializeUser(async (id, done) => { // 实时查询数据库获取用户 const users = await refresh('SELECT useruniqueid, username, password, userrole, email FROM webusers WHERE useruniqueid = ?', [id]); const user = users[0]; done(null, user); }); } module.exports = initPassport;
4. 修复权限中间件字段名
修改isAdmin函数中的字段名:
function isAdmin(req, res, next) { if (req.user.userrole === 'ADMIN') { return next(); } console.log('nope!'); return res.redirect('/'); }
内容的提问来源于stack exchange,提问作者IoT-Practitioner
相关产品推荐
相关产品推荐

