Apollo Server v4+Express+Next.js服务端Cookie设置失败求助
问题描述
在基于Apollo Server、Express和Next.js的项目中,服务端执行设置Cookie的代码无报错,但Cookie未出现在浏览器Cookie面板中,控制台也无错误提示。
服务端代码片段
index.ts
import { v2 as cloudinary } from "cloudinary"; import server from "./server"; import mongoose from "mongoose"; import dotenv from "dotenv"; /* MONGOOSE SETUP */ dotenv.config(); cloudinary.config({ secure: true, api_key: process.env.API_KEY, api_secret: process.env.API_SECRET, cloud_name: process.env.CLOUD_NAME, }); const port = Number(process.env.PORT) || 8080; const MONGO_URL = String(process.env.MONGO_URL); mongoose.set("strictQuery", true); mongoose .connect(MONGO_URL) .then(async () => { server({ port }); }) .catch((error) => console.error(`${error} did not connect`));
server.ts
import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHttpServer"; import { makeExecutableSchema } from "@graphql-tools/schema"; import { expressMiddleware } from "@apollo/server/express4"; import { useServer } from "graphql-ws/lib/use/ws"; import { PubSub } from "graphql-subscriptions"; import { ApolloServer } from "@apollo/server"; import resolvers from "./graphql/resolvers"; import typeDefs from "./graphql/typeDefs"; import cookieParser from "cookie-parser"; import { WebSocketServer } from "ws"; import bodyParser from "body-parser"; import { createServer } from "http"; import express from "express"; import cors from "cors"; export default async function server(props: { port: any }) { const pubsub = new PubSub(); const app = express(); const httpServer = createServer(app); const schema = makeExecutableSchema({ typeDefs, resolvers, }); // WebSocket Server const wsServer = new WebSocketServer({ server: httpServer, path: "/graphql", }); const serverCleanup = useServer({ schema }, wsServer); // Apollo Server const server = new ApolloServer({ schema, plugins: [ ApolloServerPluginDrainHttpServer({ httpServer }), { async serverWillStart() { return { async drainServer() { await serverCleanup.dispose(); }, }; }, }, ], }); await server.start(); // Apply Middlewares app.use( "/graphql", cors<cors.CorsRequest>({ origin: "http://localhost:3000", credentials: true, }), bodyParser.json({ limit: "10mb" }), cookieParser(), expressMiddleware(server, { context: async ({ req, res }) => { return { req, res, pubsub }; }, }) ); httpServer.listen(props.port, () => { console.log( "Server running on http://localhost:" + props.port + "/graphql" ); }); }
服务端设置Cookie的代码
console.log("Middleware executed!"); try { // 将认证令牌设置为Cookie context.res.cookie("your_cookie_name", "your_token_value", { // secure: true, // httpOnly: true, maxAge: 1000 * 60 * 30, sameSite: "none", }); console.log("Cookie set in response."); console.log("Cookies in request:", context.req.cookies); } catch (error) { console.log("context: error:", error); }
客户端代码片段
import { GraphQLWsLink } from "@apollo/client/link/subscriptions"; import { getMainDefinition } from "@apollo/client/utilities"; import { setContext } from "@apollo/client/link/context"; import { createClient } from "graphql-ws"; import { createHttpLink, InMemoryCache, ApolloClient, split, } from "@apollo/client"; const httpLink = createHttpLink({ uri: "http://localhost:8080/graphql", }); const wsLink = new GraphQLWsLink( createClient({ url: "ws://localhost:8080/graphql", }) ); const splitLink = split( ({ query }) => { const definition = getMainDefinition(query); return ( definition.kind === "OperationDefinition" && definition.operation === "subscription" ); }, wsLink, httpLink ); const createApolloClient = () => { const authLink = setContext((_, { headers }) => { return { headers: { ...headers, authorization: `Bearer token`, }, }; }); return new ApolloClient({ link: authLink.concat(splitLink), cache: new InMemoryCache(), }); }; export default createApolloClient;
观察结果
- 服务端代码执行无报错,控制台输出"Middleware executed!"和"Cookie set in response."
- 响应中设置的Cookie未在浏览器Cookie面板显示
- GraphQL与WebSocket连接正常,控制台无错误提示
环境与依赖
服务端package.json
{ "main": "server.ts", "scripts": { "compile": "tsc", "start": "nodemon ./src/index.ts", "start1": "npm run compile && node ./dist/index.js", "dev": "concurrently \"npx tsc --watch\" \"nodemon -q dist/index.js\"" }, "dependencies": { "@apollo/server": "^4.1.1", "@graphql-tools/schema": "^9.0.19", "bcrypt": "^5.1.1", "body-parser": "^1.20.1", "cloudinary": "^1.41.2", "concurrently": "^8.2.2", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", "graphql-subscriptions": "^2.0.0", "graphql-tag": "^2.12.6", "graphql-ws": "^5.11.2", "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.3", "nodemon": "^2.0.20", "ts-node": "^10.9.1", "ws": "^8.11.0" }, "devDependencies": { "@types/bcrypt": "^5.0.2", "@types/cookie-parser": "^1.4.6", "@types/cors": "^2.8.12", "@types/jsonwebtoken": "^9.0.5", "@types/ws": "^8.5.3" } }
客户端package.json
{ "name": "web", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "graphql": "^16.8.1", "graphql-ws": "^5.14.3", "next": "14.0.4", "react": "^18", "react-blurhash": "^0.3.0", "react-dom": "^18", "react-hook-form": "^7.49.3" }, "devDependencies": { "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10.0.1", "eslint": "^8", "eslint-config-next": "14.0.4", "postcss": "^8", "typescript": "^5" } }
可能的原因与解决方案
1. 客户端Apollo Client未开启凭证传递
Apollo Client默认不会携带/接收跨域Cookie,需要显式开启credentials选项:
- 修改
createHttpLink,添加credentials: 'include':
const httpLink = createHttpLink({ uri: "http://localhost:8080/graphql", credentials: 'include' // 新增此行 });
- 对于WebSocket连接,确保客户端允许携带Cookie:
const wsLink = new GraphQLWsLink( createClient({ url: "ws://localhost:8080/graphql", connectionParams: () => ({ credentials: 'include' }) }) );
2. Cookie的SameSite与Secure配置不匹配
当设置sameSite: "none"时,浏览器要求Cookie必须是secure(HTTPS环境),本地localhost环境下Chrome允许例外,但其他浏览器可能不兼容:
- 本地开发时,可暂时修改Cookie配置:
context.res.cookie("your_cookie_name", "your_token_value", { maxAge: 1000 * 60 * 30, sameSite: "lax", // 改为lax或strict // secure: true, 本地http环境注释掉,生产环境开启 });
- 生产环境必须同时开启
secure: true和sameSite: "none"(仅跨域场景需要)。
3. 确认Cookie设置的上下文正确性
确保context.res是正确的Express Response对象,并且在resolver中设置Cookie的代码执行时机正确(必须在响应发送前完成)。可通过console.log(context.res.get('Set-Cookie'))确认响应头中是否包含Set-Cookie字段:
- 若存在,说明服务端已发送Cookie,问题在客户端;
- 若不存在,检查res对象是否正确传递到上下文。
4. 检查浏览器隐私设置
部分浏览器的隐私模式或第三方Cookie拦截设置会阻止跨域Cookie,可尝试关闭隐私模式,或在浏览器设置中允许localhost的Cookie。
内容的提问来源于stack exchange,提问作者NABEEL NISAR
相关产品推荐
相关产品推荐

