CSRF令牌验证失败:服务端Cookie 'csrftoken' 未定义导致403 Forbidden错误
看起来你已经做了不少排查工作——日志都打了,也定位到了csrftoken Cookie在signup路由里是undefined的问题,这已经很关键了!我帮你梳理下核心问题点和对应的修复步骤:
核心问题诊断
你的double-csrf配置里,csrftoken是个带签名的HttpOnly Cookie,但服务端在signup时读不到它,直接导致令牌验证失败。这个Cookie本该由double-csrf的保护中间件自动生成,但显然中间件的挂载或者配置环节出了问题。
一步步修复方案
1. 先确认doubleCsrfProtection中间件被正确挂载
这个中间件是double-csrf的核心——它既要生成令牌、设置csrftoken Cookie,也要负责后续的验证。你必须把它挂载到所有需要CSRF支持的路由上,包括/csrf-token和/auth/signup:
// 要么全局挂载(所有路由都受保护/生成令牌) app.use(doubleCsrfProtection); // 要么给单个路由单独挂载 app.get('/csrf-token', doubleCsrfProtection, (req, res) => { // 你的现有代码 }); app.post('/auth/signup', doubleCsrfProtection, async (req, res) => { // 你的现有代码 });
如果没挂这个中间件,res.locals.csrfToken和csrftoken Cookie都不会生成,后续自然读不到。
2. 检查Cookie签名的配置(大概率是核心问题!)
你的csrftoken Cookie设置了signed: true,这意味着服务端必须用相同的签名密钥来解析它。如果你用了cookie-parser中间件,一定要传入和你CSRF配置一致的密钥:
const cookieParser = require('cookie-parser'); // 这里的密钥要和你double-csrf里的secret一致 app.use(cookieParser('CSRF_SECRET'));
要是没配置这个,服务端会把签名后的Cookie直接解析成undefined——这完全符合你日志里看到的现象!
3. 优化/csrf-token路由的令牌生成逻辑
你当前依赖res.locals.csrfToken,但如果中间件没正确生成,可以手动调用generateCsrfToken兜底,同时确保csrftoken Cookie被设置:
app.get('/csrf-token', doubleCsrfProtection, (req, res) => { let csrfToken = res.locals.csrfToken; // 兜底手动生成 if (!csrfToken) { csrfToken = generateCsrfToken(req, res); if (!csrfToken) { console.error("Failed to generate CSRF token manually."); return res.status(500).json({ error: "Failed to generate CSRF token." }); } } // 设置前端可读取的XSRF-TOKEN Cookie res.cookie('XSRF-TOKEN', csrfToken, { httpOnly: false, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' }); res.status(200).json({ csrfToken }); });
调用generateCsrfToken(req, res)会强制触发库设置csrftoken HttpOnly Cookie,解决生成环节的问题。
4. 确认会话标识的稳定性
你的getSessionIdentifier用了req.session.id,要确保express-session中间件配置正确,req.session确实存在且id是稳定的。可以在/csrf-token路由里加个日志确认:
console.log('当前会话ID:', req.session?.id);
如果会话ID是undefined或者每次请求都变,会导致CSRF令牌和会话绑定失败,同样会触发验证错误。
最后验证步骤
- 打开浏览器开发者工具的
Application -> Cookies,检查csrftokenCookie是否存在,属性是否符合配置(HttpOnly、路径/、SameSite=lax) - 重新请求
/csrf-token,看服务端日志有没有报错,前端能不能拿到令牌 - 发起signup请求时,确认
X-CSRF-TOKEN请求头已经正确携带,同时csrftokenCookie被浏览器自动带上
内容来源于stack exchange

