Express发送Set-Cookie但浏览器未设置的跨域问题排查
问题场景
前端运行在localhost:7000,后端运行在localhost:4000。试过多种同类问题的配置方案均无效,Express已发送Set-Cookie响应头,但浏览器在Application→Cookies中找不到该Cookie。
技术栈
- 前端:vite、react、@tanstack/react-query、graphql-request、@graphql-codegen、@graphql-codegen/typescript-react-query
- 后端:@apollo/server、type-graphql、express、express-sessions
有复现仓库可用于排查问题。
代码片段
后端服务器代码
import 'reflect-metadata'; import 'dotenv/config'; import { expressMiddleware } from '@apollo/server/express4'; import http from 'http'; import { PrismaClient } from '@prisma/client'; import { ApolloServer } from '@apollo/server'; import express from 'express'; import bodyParser from 'body-parser'; import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'; import cors, { CorsRequest } from 'cors'; import session from 'express-session'; import buildSchemaFacade from './graphql/buildSchemaFacade'; import { redisStore } from './api/redis'; const SEVEN_DAYS = 1000 * 60 * 60 * 24 * 7; const { NODE_ENV, PORT = 4000, SESSION_LIFETIME = SEVEN_DAYS, SESSION_SECRET } = process.env; async function bootstrap() { const prisma = new PrismaClient(); const app = express(); const httpServer = http.createServer(app); const schema = await buildSchemaFacade(); const server = new ApolloServer({ schema, plugins: [ApolloServerPluginDrainHttpServer({ httpServer })] }); await server.start(); app.use('/graphql', bodyParser.json()); app.use( '/graphql', cors<CorsRequest>({ origin: 'http://localhost:7000', credentials: true }) ); app.use( '/graphql', session({ proxy: true, name: 'sessionID', cookie: { maxAge: SESSION_LIFETIME as number, sameSite: 'lax', secure: NODE_ENV === 'production', httpOnly: true }, resave: false, secret: SESSION_SECRET as string, saveUninitialized: false, store: redisStore }) ); app.use( '/graphql', expressMiddleware(server, { context: async ({ req, res }) => ({ prisma, req, res }) }) ); await new Promise<void>((resolve) => httpServer.listen({ port: PORT || 4000 }, resolve) ); console.log(`🚀 Server ready at http://localhost:4000/graphql`); } bootstrap();
前端GraphQL Client代码
import { GraphQLClient } from 'graphql-request'; export const graphqlClient = new GraphQLClient(import.meta.env.VITE_GRAPHQL_ENDPOINT as string, { headers: { credentials: 'include', mode: 'cors', }, });
Login.tsx代码
import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useLoginUserMutation } from '@platform/graphql/__generated__/graphql'; import { graphqlClient } from '@platform/graphql/graphqlClient'; export default function Login() { const [error, setError] = useState<string | null>(null); const { mutate } = useLoginUserMutation(graphqlClient); const navigate = useNavigate(); const onSubmit = (event: any) => { event.preventDefault(); const email = (event.target as HTMLFormElement).email.value; const password = (event.target as HTMLFormElement).password.value; mutate( { email, password }, { onSuccess: (data) => { // navigate('/dashboard/orders'); console.log(data); }, onError: (error) => { console.error(error); setError('Something went wrong with logging in'); }, } ); }; return ( <div className="flex h-screen items-center justify-center bg-slate-100"> <div className="w-[300px] space-y-4 bg-white p-5"> <h1 className="text-xl font-bold">Login</h1> <form onSubmit={onSubmit} className="flex flex-col space-y-4 text-lg"> <div className="flex flex-col space-y-2"> <label htmlFor="email">Email</label> <input type="email" id="email" /> </div> <div className="flex flex-col space-y-2"> <label htmlFor="password">Password</label> <input type="password" id="password" /> </div> <button type="submit" className="w-fit bg-black p-2 px-4 text-white"> Login </button> </form> {error && <div className="text-red-500">{error}</div>} </div> </div> ); }
./api/redis代码
import connectRedis from 'connect-redis'; import session from 'express-session'; import RedisClient from 'ioredis'; const RedisStore = connectRedis(session); const redisClient = new RedisClient(); export const redisStore = new RedisStore({ client: redisClient }); export default redisClient;
登录请求响应截图




已尝试的解决方法
- 将Cookie的
secure字段设为true,sameSite设为none,并在GraphQL客户端添加x-forwarded-proto: https请求头,无效 - 替换GraphQL客户端为urql和apollo client,问题依旧
- 该功能在Insomnia中正常工作,但所有浏览器均不生效
- 怀疑是后端express session初始化的问题,导致浏览器不接受Set-Cookie
内容的提问来源于stack exchange,提问作者learning_developer_9
相关产品推荐
相关产品推荐

