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

为何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 04:15:39