调用/logout遇req#logout需回调函数错误,Google OAuth2.0退出问题
问题描述
调用/logout接口时触发错误:Error: req#logout requires a callback function。
期望退出登录后,再次访问认证流程时能看到完整的Google OAuth2.0登录界面,而非直接跳转到"You are authenticated"。
推测是活跃会话未清除导致,尝试添加req.logOut和req.session.destroy()清除会话,但取消注释req.logOut后持续报错,即使重新定义/logout路由也无法解决。
相关代码
index.js
const PORT = 3000; require("dotenv").config(); const express = require("express"); const app = express(); const mongoose = require("mongoose"); const passport = require("passport"); const { isLoggedIn } = require("./middlewares/authMiddleware"); const session = require("express-session"); const bodyParser = require("body-parser"); // parse application/json app.use(bodyParser.json()); // parse application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: false })); app.use(express.json()); app.use( session({ secret: process.env.SESSION_SECRET, resave: false, // don't save session if unmodified saveUninitialized: false, // don't create session until something stored }) ); app.use(passport.initialize()); app.use(passport.session()); // Import routes const userRoute = require("./app/routes/UserController"); // Route middleware app.use("/api/v1/users", userRoute); app.get("/", (req, res) => { res.send('<a href="/auth/google">Authenticate with Google</a>'); }); app.get( "/auth/google/callback", passport.authenticate("google", { successRedirect: "/protected", failureRedirect: "/auth/failure", }) ); app.get("/auth/failure", (req, res) => { res.send("Failed to authenticate.."); }); app.get( "/auth/google", passport.authenticate("google", { scope: ["email", "profile"] }) ); app.get("/protected", isLoggedIn, (req, res) => { res.send("You are authenticated"); }); app.get("/logout", (req, res, next) => { if (req.session) { req.logOut(); req.session.destroy(); res.redirect("/"); } }); /** * Configure mongoose */ // mongoose.Promise = global.Promise; app.locals.db = mongoose .connect(process.env.DB_CONNECT, { useNewUrlParser: true, useUnifiedTopology: true, }) .then(() => { console.log("Connected to Database"); app.listen(PORT, () => { console.log(`Server listening on port ${PORT}`); }); });
authMiddleware.js
const passport = require("passport"); const GoogleStrategy = require("passport-google-oauth20").Strategy; const User = require("../app/models/User"); passport.serializeUser((user, done) => { done(null, user); }); passport.deserializeUser(function (user, done) { done(null, user); }); passport.use( new GoogleStrategy( { clientID: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, callbackURL: process.env.GOOGLE_CALLBACK_URL, }, function (accessToken, refreshToken, profile, done) { User.findOne({ "oauth.googleId": profile.id }) .then((existingUser) => { if (existingUser) { return done(null, existingUser); } else { var newUser = new User({ name: profile.givenName, surname: profile.familyName, email: profile.emails[0].value, username: profile.displayName, oauth: { googleId: profile.id, displayName: profile.displayName, firstName: profile.name.givenName, lastName: profile.name.familyName, email: profile.emails[0].value, }, }); newUser .save() .then((newUser) => { return done(null, newUser); }) .catch((err) => { return done(err); }); } }) .catch((err) => { return done(err); }); } ) ); module.exports.isLoggedIn = function (req, res, next) { req.user ? next() : res.sendStatus(401); }; module.exports.passport = passport;
解决方案
1. 修复req.logOut()的回调错误
旧版Passport的req.logOut()必须传入回调函数,不能直接调用。修改/logout路由:
app.get("/logout", (req, res, next) => { req.logOut((err) => { if (err) return next(err); req.session.destroy((err) => { if (err) return next(err); res.clearCookie("connect.sid"); // 清除session对应的cookie res.redirect("/"); }); }); });
若使用Passport 0.6.0+版本,req.logOut()支持Promise语法,可简化为:
app.get("/logout", async (req, res, next) => { try { await req.logOut(); await req.session.destroy(); res.clearCookie("connect.sid"); res.redirect("/"); } catch (err) { next(err); } });
2. 强制显示Google OAuth2.0登录界面
Google默认会保留用户登录状态,即使后端会话清除,再次访问认证路由会自动登录。需在认证路由添加prompt: "select_account"参数:
app.get( "/auth/google", passport.authenticate("google", { scope: ["email", "profile"], prompt: "select_account" // 强制弹出账号选择/登录界面 }) );
3. 确保会话彻底清除
- 调用
req.session.destroy()销毁服务器端会话数据 - 使用
res.clearCookie()清除客户端的session cookie(默认名为connect.sid,若自定义session的name参数需对应修改)
内容的提问来源于stack exchange,提问作者posky
相关产品推荐
相关产品推荐

