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

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:谷歌用户唯一ID
  • profile.emails[0].value:用户主邮箱地址
  • profile.displayName:用户显示名称
  • profile.name.givenName/profile.name.familyName:用户的名/姓

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 16:20:21