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

Passport.js无法登出且无法删除Cookie问题求助

问题描述

我用Node.js开发了一个示例应用,基于Passport.js实现用户注册、登录和登出功能。注册和登录流程都能正常运行,但执行登出操作时,终端仅输出反序列化函数的日志,浏览器中的Cookie没有变化,调用logout函数后仍然能获取到req.user,用户并未真正登出。

相关代码

中间件配置

//middleware
app.use(session({
    secret: "secret",
    resave: true,
    saveUninitialized: true,
    cookie: {maxAge:60*60*1000}//1hour
}));
app.use(express.urlencoded({
    extended: false
 }));
 app.use(express.json());
app.use(cors({
    origin: "http://localhost:3000",
    credentials: true,
}));
app.use(cookieParser("secret"));
app.use(passport.initialize());
app.use(passport.session());

Passport序列化/反序列化函数

passport.serializeUser((user, cb) => {
    console.log("serializing user");
    cb(null, user.id);
});
passport.deserializeUser((id, cb) => {
    User.findById(id).then((err, user) => {
        if(err) throw err;
        console.log("id: " + id);
        console.log("userid: " + user);
        const userInformation = {
            username: user.email,
        };
        cb(err, user);
    }).catch((err) => {console.error(err)});
});

LocalStrategy配置

passport.use(
    new localStrategy(async (username, password, done) => {
        try{
            const user = await User.findOne({username: username});
            if(!user) {
                console.log("no user");
                return done(null, false);
            }
            bcrypt.compare(password, user.password, (err, result) => {
                if(err) throw err;
                if(result === true) {
                    return done(null, user);
                } else {
                    return done(null, false);
                }
            });
        } catch(err){
            console.log("err inside localStrategy: " + err);
            throw err;

        }
    }) 
);

路由处理

登录路由

app.post("/login", (req, res) => {
    console.log("inside login");

    passport.authenticate("local", (err, user, info) => {
        console.log("inside authenticate, user: " + user);
        if(err) {
            console.log("err: " + err);
            throw err;
        }
        if(!user) {
            console.log("No user");
            res.status(400);
            res.send("No user exists");
        }
        else {
            console.log("inside else");
            req.logIn(user, (err) => {
                if(err) throw err;
                console.log("req.user: " + req.user);
                console.log("successfully authenticated");
                res.status(200).json({message: "successfully authenticated here"});
            });
        }
    })(req, res);
});

注册路由

app.post("/register", async (req, res) => {
    
    try {
        console.log("username: " + req.body.username);
        const existingUser = await User.findOne({username: req.body.username});
        if(existingUser) {
            console.log("duplicate user");
            return res.status(400).json({message: "User already exists"});
        }
        console.log("password to be hashed: " + req.body.password);
        //hash password
        const newUser = new User({
            username: req.body.username,
            password: req.body.password
        });
        bcrypt.genSalt(10, (err,salt)=>
            bcrypt.hash(newUser.password, salt,
                (err, hash) =>{
                    if(err) throw err;
                    newUser.password = hash;
                    newUser.save()
                    .then((value)=> {
                        console.log("value: " + value);
                        console.log("registered: " + newUser.username);
                        return res.status(200).json({message: "User successfully registered"});
                    })
                    .catch(err => console.log(err));
                }
            )
        );
    }
    catch (err) {
        console.log("error registering: " + err);
        res.status(500).json({message: "Internal srever error"});
    }
});

登出路由

app.get("/logout", function(req, res){
    req.logout();
    req.session.destroy(function(err){
        if(err){
            console.log("error: " + err);
            res.status(500).json({message: "Error destroying session"});
        } else {
            console.log("destroying session");
            res.clearCookie('mcookie');
            res.status(200).json({message: "Logged out successfully"});
        }
    });
});
问题分析与解决方案

核心问题点

  1. 反序列化函数写法错误:Mongoose的findById返回的Promise,成功回调的第一个参数是查询结果而非错误。当前代码把用户数据当成错误传入cb(err, user),导致Passport无法正确处理会话状态,还会触发不必要的反序列化日志。
  2. 登出逻辑不完整:
    • 新版本Passport的req.logout()是异步操作,未等待完成就销毁会话会导致清理不彻底。
    • 清理Cookie时使用了错误的名称:express-session默认会话Cookie名称是connect.sid,不是mcookie,导致Cookie未被清除。
    • 未手动清除req.user,导致后续请求仍能读取到用户信息。

修复步骤

1. 修正反序列化函数

passport.deserializeUser((id, cb) => {
    User.findById(id)
        .then(user => {
            if (!user) {
                return cb(new Error("User not found"));
            }
            console.log("id: " + id);
            console.log("user: " + user);
            cb(null, user);
        })
        .catch(err => {
            console.error(err);
            cb(err);
        });
});

2. 修正登出路由

app.get("/logout", async function(req, res){
    // 异步执行logout,确保会话状态清理完成
    await new Promise((resolve, reject) => {
        req.logout(err => {
            if (err) reject(err);
            else resolve();
        });
    });

    req.session.destroy(function(err){
        if(err){
            console.log("error: " + err);
            return res.status(500).json({message: "Error destroying session"});
        } 
        // 清理默认的会话Cookie,匹配session配置的参数
        res.clearCookie('connect.sid', {
            path: '/',
            httpOnly: true,
            secure: process.env.NODE_ENV === 'production',
            sameSite: 'lax'
        });
        // 手动清除req.user
        req.user = null;
        console.log("destroying session");
        res.status(200).json({message: "Logged out successfully"});
    });
});

3. 优化Session配置(推荐)

app.use(session({
    secret: "your-strong-secret-here", // 替换为安全的随机密钥
    resave: false, // 避免不必要的会话存储
    saveUninitialized: false, // 不存储未初始化的会话,符合隐私规范
    cookie: {
        maxAge: 60*60*1000,
        httpOnly: true, // 防止XSS攻击
        secure: process.env.NODE_ENV === 'production', // 生产环境启用HTTPS
        sameSite: 'lax' // 防范CSRF攻击
    }
}));

额外注意事项

  • 前端发起登出请求时,需设置credentials: true,否则浏览器不会传递Cookie,导致清理失败。
  • 生产环境下,Session密钥不要硬编码,改用环境变量管理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:54:55