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

调用/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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 18:54:54