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, };
验证修复
- 重启服务后完成Google认证流程
- 在Apollo Studio发起查询时,确认请求携带
mySessioncookie - 在上下文打印
req.session和req.user,验证数据存在
内容的提问来源于stack exchange,提问作者Vytautas
相关产品推荐
相关产品推荐

