Node.js Passport JWT认证异常:生成Token正常但路由保护报错
解决Passport JWT认证中
Cannot read property '_id' of undefined的问题 我一眼就看出问题出在JWT Payload的结构上,咱们一步步拆解解决:
问题根源
你在登录接口里用user.toJSON()生成JWT的Payload:
var token = jwt.sign(user.toJSON(), config.secret, {expiresIn: 1000 });
Mongoose的toJSON()方法会自动移除文档内部的特殊属性(比如$__、_doc这些框架私有的字段),所以最终生成的JWT Payload里根本没有$__这个对象,自然就找不到$__._id,这就是报错的核心原因。
另外,你用的ExtractJwt.fromAuthHeader()已经被passport-jwt标记为过时,建议换成更标准的提取方式,避免后续踩坑。
具体修复步骤
1. 重构JWT生成逻辑,只传递必要字段
别直接序列化整个Mongoose文档,而是提取你需要的核心用户信息(比如_id、email)来生成JWT,这样Payload结构更清晰,也不会带多余的内部属性:
// 修改后的登录接口代码 router.post('/login', function(req, res) { User.findOne({ email: req.body.email }, function(err, user) { if (err) throw err; if (!user) { res.send({ success: false, message: 'Authentication failed. User not found.' }); console.log('User not found'); } else { user.comparePassword(req.body.password, function(err, isMatch) { if (isMatch && !err) { // 手动提取需要的字段作为Payload const userPayload = { _id: user._id, email: user.email }; var token = jwt.sign(userPayload, config.secret, {expiresIn: 1000 }); var decoded = jwt.decode(token); console.log(decoded); // 换成标准的Bearer Token格式 res.json({ success: true, token: 'Bearer ' + token, decoded: decoded }); console.log('Connected : ' + token); } else { res.send({ success: false, message: 'Authentication failed. Passwords did not match.' }); console.log('Password is wrong'); } }); } }); });
2. 修改Passport验证逻辑,直接读取Payload中的_id
既然我们已经把_id直接放在Payload顶层了,就不需要再去访问不存在的$__属性了:
// 修改后的passport.js代码 var JwtStrategy = require('passport-jwt').Strategy; var ExtractJwt = require('passport-jwt').ExtractJwt; var User = require('../models/User'); var config = require('./database'); module.exports = function(passport) { var opts = {}; // 使用最新的Bearer Token提取方式 opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); opts.secretOrKey = config.secret; passport.use(new JwtStrategy(opts, function(jwt_payload, done) { // 直接读取jwt_payload._id User.findById(jwt_payload._id, function(err, user) { if (err) { return done(err, false); } if (user) { done(null, user); } else { done(null, false); } }); })); };
3. 调整请求头格式(关键)
现在我们用了Bearer前缀的Token,前端请求/dashboard时,必须在请求头里设置:
Authorization: Bearer <你的Token内容>
如果你之前习惯用JWT前缀,也可以保留,但要同步修改提取方式:
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme('jwt');
额外提醒
- 绝对不要把敏感信息(比如密码哈希)放进JWT Payload里,JWT是可解码的,内容完全公开,只能靠签名保证不被篡改。
- 你设置的
expiresIn: 1000是1000秒(约16分钟),测试没问题,生产环境可以根据需求调整时长。
内容的提问来源于stack exchange,提问作者Valentin Valentyn
相关产品推荐
相关产品推荐

