为何在passport.use()中无法访问req.user?需实现Google账号关联
如何让已登录用户关联Google账号(Passport.js中获取req.user)
我正在开发一个基于PostgreSQL的小型电商应用,采用Google第三方登录方案,当前的Google Strategy配置及login回调函数对新注册和已有Google登录用户均能正常工作,还可引导新注册用户设置手机号和密码。现在我需要添加功能,让非Google注册的现有用户关联其Google账号,但发现即使已登录,passport.use()中的req.user仍为undefined。请问如何在passport.use()中获取当前会话的用户信息,或有其他解决方案?
现有Google Strategy配置
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy; passport.use(new GoogleStrategy({ clientID: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, callbackURL: `${process.env.NODE_ENV === "production" ? [production URL] : "http://localhost:8000"}/api/auth/login/google/callback`, passReqToCallback: true, scope: ["email", "profile"] }, db.third.login));
现有db.third.login回调代码
const login = async(req, accessToken, refreshToken, profile, done) => { // Get request IP address const ip = requestIP.getClientIp(req); // Generate login attempt ID const attemptId = idGen(15); try { // Get federated credentials let result = await pool.query("SELECT * FROM federated_credentials WHERE id = $1 AND provider = $2", [profile.id, profile.provider]); // Create user account if credentials don't exist if (result.rows.length === 0) { // Send error if email already exists in database result = await pool.query("SELECT email FROM users WHERE email = $1", [profile.emails[0].value]); if (result.rows.length > 0) return done({ status: 409, message: "Error: A user with the provided email already exists." }); // Generate user ID and cart ID const userId = idGen(7); const cartId = idGen(7); // Generate password hash const salt = await bcrypt.genSalt(17); const passwordHash = await bcrypt.hash(process.env.GENERIC_PASSWORD, salt); // Add user to database let text = `INSERT INTO users (id, first_name, last_name, phone, email, password, created_at) VALUES ($1, $2, $3, $4, $5, $6, to_timestamp(${Date.now()} / 1000)) RETURNING id`; let values = [userId, profile.name.givenName, profile.name.familyName, "254700000000", profile.emails[0].value, passwordHash]; result = await pool.query(text, values); // Add user cart to database result = await pool.query("INSERT INTO carts (id, user_id) VALUES ($1, $2)", [cartId, userId]); // Add federated credentials to database result = await pool.query("INSERT INTO federated_credentials (id, provider, user_id) VALUES ($1, $2, $3)", [profile.id, profile.provider, userId]); // Add user details to be confirmed to session const federatedCredentials = { id: profile.id, provider: profile.provider, confirm: true }; return done(null, { id: userId, email: profile.emails[0].value, role: "customer", cartId: cartId, federatedCredentials }); } // Save federated credentials details const federatedCredentials = { id: result.rows[0].id, provider: result.rows[0].provider, confirm: !result.rows[0].confirmed }; // Get user details result = await pool.query("SELECT users.id AS id, users.email AS email, users.password AS password, users.role AS role, carts.id AS cart_id FROM users JOIN carts ON carts.user_id = users.id WHERE email = $1", [profile.emails[0].value]); // Log login attempt await loginAttempt(attemptId, ip, profile.emails[0].value, "google", true); // Add user to session return done(null, { id: result.rows[0].id, email: result.rows[0].email, role: result.rows[0].role, cartId: result.rows[0].cart_id, federatedCredentials }); } catch (err) { return done({ status: 500, message: "An unknown error occurred. Kindly try again." }); } }
解决方案
1. 确认会话中间件加载顺序
req.user依赖会话机制,必须确保express-session在Passport相关中间件之前加载:
// 先初始化会话 app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, // 其他会话配置(如存储方式、过期时间等) })); // 再初始化Passport app.use(passport.initialize()); app.use(passport.session());
2. 调整回调逻辑,支持账号关联
你的代码已经开启passReqToCallback: true,可以直接获取req.user。修改login回调,区分已登录用户关联账号和未登录用户登录/注册两种场景:
const login = async(req, accessToken, refreshToken, profile, done) => { const ip = requestIP.getClientIp(req); const attemptId = idGen(15); try { // 场景1:用户已登录,执行Google账号关联 if (req.user) { // 检查该Google账号是否已绑定其他用户 let result = await pool.query("SELECT * FROM federated_credentials WHERE id = $1 AND provider = $2", [profile.id, profile.provider]); if (result.rows.length > 0) { return done({ status: 409, message: "该Google账号已绑定到其他用户" }); } // 将Google账号关联到当前登录用户 await pool.query("INSERT INTO federated_credentials (id, provider, user_id) VALUES ($1, $2, $3)", [profile.id, profile.provider, req.user.id]); // 更新会话中的用户信息(可选) req.user.federatedCredentials = { id: profile.id, provider: profile.provider, confirm: false }; return done(null, req.user); } // 场景2:用户未登录,执行原有登录/注册逻辑 let result = await pool.query("SELECT * FROM federated_credentials WHERE id = $1 AND provider = $2", [profile.id, profile.provider]); if (result.rows.length === 0) { result = await pool.query("SELECT email FROM users WHERE email = $1", [profile.emails[0].value]); if (result.rows.length > 0) return done({ status: 409, message: "Error: A user with the provided email already exists." }); const userId = idGen(7); const cartId = idGen(7); const salt = await bcrypt.genSalt(17); const passwordHash = await bcrypt.hash(process.env.GENERIC_PASSWORD, salt); let text = `INSERT INTO users (id, first_name, last_name, phone, email, password, created_at) VALUES ($1, $2, $3, $4, $5, $6, to_timestamp(${Date.now()} / 1000)) RETURNING id`; let values = [userId, profile.name.givenName, profile.name.familyName, "254700000000", profile.emails[0].value, passwordHash]; result = await pool.query(text, values); await pool.query("INSERT INTO carts (id, user_id) VALUES ($1, $2)", [cartId, userId]); await pool.query("INSERT INTO federated_credentials (id, provider, user_id) VALUES ($1, $2, $3)", [profile.id, profile.provider, userId]); const federatedCredentials = { id: profile.id, provider: profile.provider, confirm: true }; return done(null, { id: userId, email: profile.emails[0].value, role: "customer", cartId: cartId, federatedCredentials }); } const federatedCredentials = { id: result.rows[0].id, provider: result.rows[0].provider, confirm: !result.rows[0].confirmed }; result = await pool.query("SELECT users.id AS id, users.email AS email, users.password AS password, users.role AS role, carts.id AS cart_id FROM users JOIN carts ON carts.user_id = users.id WHERE email = $1", [profile.emails[0].value]); await loginAttempt(attemptId, ip, profile.emails[0].value, "google", true); return done(null, { id: result.rows[0].id, email: result.rows[0].email, role: result.rows[0].role, cartId: result.rows[0].cart_id, federatedCredentials }); } catch (err) { return done({ status: 500, message: "An unknown error occurred. Kindly try again." }); } }
3. 添加关联入口路由
给已登录用户提供专门的Google关联入口,搭配登录验证中间件:
// 已登录用户触发Google账号关联的路由 app.get("/api/auth/link/google", isAuthenticated, passport.authenticate("google", { scope: ["email", "profile"] })); // 关联回调路由 app.get("/api/auth/link/google/callback", isAuthenticated, passport.authenticate("google", { failureRedirect: "/profile" }), (req, res) => { // 关联成功后跳转回用户中心 res.redirect("/profile"); }); // 验证用户是否登录的中间件 function isAuthenticated(req, res, next) { if (req.isAuthenticated()) { return next(); } res.redirect("/login"); }
内容的提问来源于stack exchange,提问作者Davy Kamanzi
相关产品推荐
相关产品推荐

