Passport.js GoogleStrategy异常:无法读取undefined的属性'0'
问题描述
实现Passport.js谷歌登录时遇到以下异常:
- 打印
profile对象输出长字符串,而非预期的结构化对象 profile.id返回undefined,与官方文档描述不符- 执行
profile.emails[0].value时抛出错误:Cannot read properties of undefined (reading '0') - 不希望依赖Passport内置会话支持,已自行实现
create_user和login_user会话处理函数
相关代码如下:
const passport = require('passport'); const GoogleStrategy = require('passport-google-oidc'); const User = require('../models/user-schema'); async function create_user(name, email) { // 代码过长未展示,默认可正常工作(与当前问题无关) const user = await User.create({}); return login_user(user) } function login_user(user) { req.session.user_id = user._id; delete user._doc.hash; delete user._doc.salt; return user; } passport.use(new GoogleStrategy({ clientID: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SEC, callbackURL: 'http://localhost:4000/auth/login-google/redirect/', scope: [ 'email', 'profile' ] }, async function(request, accessToken, refreshToken, profile, done) { try { console.log(profile); // 莫名输出长字符串 console.log(profile.id); // 文档中可使用,但此处返回undefined const email = profile.emails[0].value; // 此处抛出错误,而我看到其他人都是这么用的 // (!) // 由于profile对象似乎不是一个对象,后续代码尚未能测试 // (!) const existing_user = await User.findOne({ email: email }); const user = (existing_user) ? login_user(existing_user) : await create_user(profile.displayName, email); return done(null, user); } catch (err) { console.log('errror: ' + err.message); return done(err); } })); router.get('/login-google', passport.authenticate('google')); router.get('/login-google/redirect/', passport.authenticate('google', { successRedirect: '/login-google/success', failureRedirect: '/login-google/failure' })); router.get('/login-google/success', (req, res) => { console.log('success'); }); router.get('/login-google/failure', (req, res) => { console.log('failure'); });
解决方案
1. 修正回调参数顺序(核心问题)
passport-google-oidc策略的回调参数顺序和传统OAuth2策略不同,它的回调只接收(request, profile, done)三个参数,你额外添加的accessToken和refreshToken导致参数错位:原本的profile被当成accessToken(即你看到的长字符串),真正的profile参数被挤到了未使用的位置,自然无法访问其属性。
修正后的回调函数:
async function(request, profile, done) { try { console.log(profile); // 现在会输出正常的用户对象 console.log(profile.id); // 可正常获取谷歌用户ID const email = profile.emails[0].value; // 可正常获取邮箱 const existing_user = await User.findOne({ email: email }); const user = (existing_user) ? login_user(request, existing_user) : await create_user(request, profile.displayName, email); return done(null, user); } catch (err) { console.log('error: ' + err.message); return done(err); } }
2. 禁用Passport内置会话
既然使用自定义会话逻辑,需要在passport.authenticate中显式禁用会话支持,避免Passport自动创建会话:
router.get('/login-google/redirect/', passport.authenticate('google', { successRedirect: '/login-google/success', failureRedirect: '/login-google/failure', session: false // 禁用Passport内置会话 }));
同时无需配置passport.serializeUser和passport.deserializeUser方法。
3. 修复login_user函数的req未定义问题
login_user函数中直接使用req会导致报错,需要将req作为参数传入:
function login_user(req, user) { req.session.user_id = user._id; delete user._doc.hash; delete user._doc.salt; return user; }
同时修改create_user函数内部对login_user的调用,同步传入req参数:
async function create_user(req, name, email) { const user = await User.create({}); return login_user(req, user) }
4. 验证修正后的profile结构
参数修正后,profile会恢复为标准对象,可正常访问以下属性:
profile.id:谷歌用户唯一IDprofile.emails[0].value:用户主邮箱地址profile.displayName:用户显示名称profile.name.givenName/profile.name.familyName:用户的名/姓
内容的提问来源于stack exchange,提问作者Oscar
相关产品推荐
相关产品推荐

