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

Express.js集成CSRF出现ForbiddenError: invalid csrf token问题求助

Express.js CSRF防护无效问题排查与修复

问题背景

在Express.js项目中集成CSRF防护后,登录操作触发ForbiddenError: invalid csrf token错误,配置后仍无法正常通过验证,相关代码如下(已整理):

排查出的核心问题

  1. EJS模板嵌套form标签:login.ejs中外层form嵌套了内层form,浏览器对嵌套form的解析存在问题,导致提交时仅内层form的字段被发送,用户名、密码和CSRF token无法同时被正确传递。
  2. Session配置键名错误:App.js中session配置使用了tasks键名,正确的存储配置键应为store,导致Session未正确持久化到MongoDB,CSRF token依赖Session存储,因此验证失败。
  3. 手动CSRF验证逻辑错误:UserController.js的postLoginUser中手动调用req.csrfToken()与提交的token对比,但该方法每次调用都会生成新的token,永远无法匹配,且csurf中间件已自动完成验证,无需手动重复验证。
  4. 中间件顺序错误:body-parser中间件放在了csrf防护之后,导致csrf中间件无法读取请求体中的token参数。

修复方案及代码修正

1. 修复login.ejs的嵌套form问题

将内层form标签移除,把CSRF token隐藏域和提交按钮移到外层form内:

<!-- 修正后的login.ejs表单部分 -->
<h2 class="title">Log in</h2>
<form action="/login-user" method="post">
    <!--Username -->
    <div class="row row-space">
        <div class="col-2">
            <div class="input-group">
                <label class="label">Username</label>
                <input class="input--style-4" type="text" name="username">
            </div>
        </div>
    </div>
    <!--Password -->
    <div class="row row-space">
        <div class="col-2">
            <div class="input-group">
                <label class="label">Password</label>
                <input class="input--style-4" type="password" name="password">
            </div>
        </div>
    </div>
    <!-- Login button press-->
    <div class="p-t-15">
        <div>
            <input type="hidden" name="_csrf" value="<%= csrfToken %>">
            <button class="login-btn btn--radius-2 btn--blue" type="submit">Log in</button>
        </div>
    </div>
</form>

2. 修正App.js的Session配置

将session配置中的tasks改为store:

// App.js中session配置修正
app.use(session({
    secret: 'my secret',
    resave: false,
    saveUninitialized: false,
    store : tasks // 替换原有的tasks键名
}))

3. 调整中间件顺序并移除手动验证逻辑

  • 将body-parser移到session和csrf中间件之前;
  • 移除UserController.js中手动验证CSRF token的代码块:

修正后的App.js中间件顺序

// App.js中间件顺序调整
app.use(bodyParser.urlencoded({ extended: false })) // 移到最前面
app.use(express.static(path.join(__dirname, '..', '..', 'public'))); 
app.use(session({
    secret: 'my secret',
    resave: false,
    saveUninitialized: false,
    store : tasks
})) 
app.use(csrfProtection); 

修正后的postLoginUser方法

// UserController.js的postLoginUser修正
exports.postLoginUser = (req, res, next) => {
    const username = req.body.username;
    const password = req.body.password;

    // 移除手动验证csrf token的代码块

    User.findOne({username: username})
        .then((user) => {
            if(!user){
                console.log('User field is empty');
                res.redirect('/login');
            }
            bcrypt.compare(password, user.password)
                .then(passwordMatch => {
                    if(passwordMatch){
                        req.session.isLoggedin = true;
                        req.session.user = user 
                        console.log('Password matched saving session and redirecting...')
                        return req.session.save((err) => {
                            if(err){
                                console.log('req.session.save error: ',err);
                            }else{
                                res.redirect('/');
                            }
                        })
                    }else{
                        console.log('Password is not correct redirecting back to login page.')
                        res.redirect('/login');
                    }
                })
        }).catch(err => {
            console.log('findOne error is: ', err);
    })
}

验证修复效果

完成上述修改后,重启服务,登录时CSRF token将被正确传递并验证,可正常完成登录流程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 13:49:50