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

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))');
}

问题排查与解决方案

核心问题点

  1. 序列化字段名不匹配:Passport配置中serializeUser尝试读取user.id,但数据库用户ID字段为useruniqueid,导致获取到undefined,触发序列化失败。
  2. Passport初始化时机错误:路由文件中通过webUsers.then()异步初始化Passport,但Express的Passport中间件(passport.initialize()、passport.session())先于该回调执行,可能导致登录请求到来时Passport未完成初始化。
  3. 用户数据依赖静态数组:一次性拉取用户到内存数组,后续数据库更新(注册、删除)不会同步到数组,导致验证/反序列化时数据不一致。
  4. 权限中间件字段名错误: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 07:48:14