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

Node.js+Express中Apollo GraphQL上下文req.user/req.session.passport未定义排查

问题:Passport+Express Session认证后,Apollo Server v4上下文丢失req.session.passport与req.user

我正在用Passport Google策略+Express Session实现用户认证,想通过Apollo Server v4的上下文传递用户数据,但在解析器中访问上下文时,req.session.passport和req.user莫名消失。相关代码如下:

server.ts

import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHttpServer";
import { expressMiddleware } from "@apollo/server/express4";
import { WebSocketServer } from "ws";
import { useServer } from "graphql-ws/lib/use/ws";
import { ApolloServer } from "@apollo/server";
import express from "express";
import http from "http";
import cors from "cors";
import "dotenv/config";
import mongoose from "mongoose";
import { schema, setHttpPlugin } from "./serverSettings/config";
import { json } from "body-parser";
import cookieParser from "cookie-parser";
import passport from "passport";
import authRoutes from "./routes/auth";
import "./services/passport";
import { expressSession } from "./services/session";

//config variables
const port = process.env.PORT;
const host = process.env.HOST;
const dbUri = process.env.DB_URI;

//connect to DB
mongoose
  .connect(dbUri)
  .then(() => {
    console.log("DB connected!");
    startApolloServer();
  })
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

const startApolloServer = async () => {
  const app = express();

  const httpServer = http.createServer(app);

  const wsServer = new WebSocketServer({
    server: httpServer,
    path: "/graphql",
  });

  const serverCleanup = useServer({ schema }, wsServer);

  const server = new ApolloServer({
    schema,
    plugins: [
      ApolloServerPluginDrainHttpServer({ httpServer }),
      setHttpPlugin,
      {
        async serverWillStart() {
          return {
            async drainServer() {
              await serverCleanup.dispose();
            },
          };
        },
      },
    ],
  });

  await server.start();

  app.use(expressSession);
  app.use(passport.initialize());
  app.use(passport.session());

  const corsOptions = {
    origin: "https://studio.apollographql.com",
    credentials: true,
  };
  app.use(
    "/graphql",
    // isLoggedIn,
    cors<cors.CorsRequest>(corsOptions),
    json(),
    cookieParser(),
    expressMiddleware(server, {
      context: async ({ req }: any) => {
        // console.log("session --->", req.session);
        // console.log("user --->", req.user);
        return { req };
      },
    })
  );

  //Google Auth
  app.get("/", (req, res) => {
    res.send('<a href="/auth/google">Auth with Google</a>');
  });

  app.use("/auth", authRoutes);

  //check if user is auth
  // function isLoggedIn(req: Request, res: Response, next: NextFunction) {
  //   req.user ? next() : res.sendStatus(401);
  // }

  await new Promise<void>((resolve) => httpServer.listen({ port }, resolve));
  console.log(`🚀 Server ready at ${host}:${port}/graphql`);
};

passport.ts

import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import passport from "passport";
import "dotenv/config";
import Settings from "../models/Settings";

const googleClientId = process.env.GOOGLE_CLIENT_ID;
const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
const callbackURL = process.env.GOOGLE_OAUTH_REDIRECT_URL;

passport.serializeUser(function (profile: any, done) {
  done(null, profile.id);
});

passport.deserializeUser(function (id: string, done) {
  done(null, id);
});

passport.use(
  new GoogleStrategy(
    {
      clientID: googleClientId,
      clientSecret: googleClientSecret,
      callbackURL,
      passReqToCallback: true,
    },
    async (request, accessToken, refreshToken, profile, done) => {
      await Settings.collection.drop();
      await new Settings({ refreshToken }).save();
      done(null, profile);
    }
  )
);

session.ts

import session from "express-session";
import MongoStore from "connect-mongo";
import "dotenv/config";

const dbUri = process.env.DB_URI;
const sessionSecret = process.env.SESSION_SECRET;

export const expressSession = session({
  name: "mySession",
  secret: sessionSecret,
  store: MongoStore.create({
    mongoUrl: dbUri,
  }),
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    maxAge: 1000 * 60 * 60 * 24,
  },
});

请求上下文截图


核心问题与修复步骤

1. 中间件顺序错误

Express中间件执行顺序直接影响功能,cookieParser必须在express-session之前加载,否则session无法读取请求中的cookie。你的代码仅在/graphql路由内使用cookieParser,且全局中间件顺序颠倒。

修复:
在全局中间件中调整顺序,先加载cookieParser:

// 在startApolloServer函数中修改
app.use(cookieParser()); // 优先加载cookie解析
app.use(expressSession);
app.use(passport.initialize());
app.use(passport.session());

同时移除/graphql路由内的cookieParser(),避免重复加载导致冲突。

2. Session Cookie跨域配置缺失

Apollo Studio属于跨域环境,默认的session cookie配置无法被浏览器跨域携带,需要调整sameSite和secure属性。

修改session.ts:

export const expressSession = session({
  name: "mySession",
  secret: sessionSecret,
  store: MongoStore.create({
    mongoUrl: dbUri,
  }),
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    maxAge: 1000 * 60 * 60 * 24,
    sameSite: 'none', // 允许跨域请求携带cookie
    secure: true, // 仅HTTPS环境生效(Apollo Studio为HTTPS)
  },
});

本地测试时可暂时将secure设为false,但生产环境必须开启。

3. 优化Passport反序列化逻辑

当前deserializeUser直接返回用户ID,导致req.user仅为字符串。若需完整用户数据,应从数据库查询:

passport.deserializeUser(async function (id: string, done) {
  try {
    // 替换为你的用户查询逻辑
    const user = await User.findById(id);
    done(null, user);
  } catch (err) {
    done(err);
  }
});

4. 完善CORS来源配置

确保CORS的origin包含所有允许的请求来源,比如本地开发地址:

const corsOptions = {
  origin: ["https://studio.apollographql.com", "http://localhost:3000"],
  credentials: true,
};

验证修复

  1. 重启服务后完成Google认证流程
  2. 在Apollo Studio发起查询时,确认请求携带mySession cookie
  3. 在上下文打印req.session和req.user,验证数据存在

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 21:15:17