在尝试了不同的配置后发现类似的问题,他们似乎都没有工作。设置Cookie是由快递发送,但浏览器没有设置它在应用程序-〉Cookie
此问题适用于我在localhost:7000
上运行前端而在localhost:4000
上运行后端的情况
前端技术:vite,react,@tansack/react-查询,图形请求(提取器),@图形代码生成器,@图形代码生成器/typescript-react-查询(使用此函数为图形生成React查询钩子)
后端技术:@apollo/服务器,类型-图形ql,快速,快速会话
再生产回购:https://github.com/Waqas-Abbasi/cookies-not-set-repro
后端服务器:
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客户端:
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>
);
}
按下登录按钮时的响应:
第一节第一节第一节第一节第二节第一节第三节第一节
编辑:
我也尝试了解决方案:
https://community.apollographql.com/t/cookie-not-shown-stored-in-the-browser/1901/8
将cookie的安全字段设置为true
,将sameSite设置为none
,并在graphQL客户端中传递值为https
的x-forwarded-proto
。
尽管如此,它仍然不起作用。顺便说一句,它在失眠症上的工作和预期的一样,只是在任何浏览器上都不起作用
编辑2:我也尝试过用urql和apolloclient替换graphql-request,还是同样的问题,这让我想到这可能是后端的问题,比如express会话是如何初始化的,而且由于某种原因,浏览器不喜欢从后端发送的Set-Cookie
编辑3:./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;
编辑四:
再生产回购:https://github.com/Waqas-Abbasi/cookies-not-set-repro
3条答案
按热度按时间yacmzcpb1#
在GraphQL客户端定义中,将
与
因为它们不是报头,所以它们是请求的参数。
如果请求中没有
credentials: 'include'
参数,fetch
方法将既不会发送也不会接收任何 cross-origin cookie,其中端口的差异(localhost:7000
vs.localhost:4000
)已经使请求跨源。Wesley LeMahieu的答案涉及到当一个cookie被算作 same-site 时的问题,其中端口在获取一个站点时并不重要。
localhost
和127.0.0.1
不是same-site。只有当
fetch
请求是同一站点时,才会发送或接收带有SameSite: Lax
的Cookie。在问题中,fetch
请求是同一站点,但跨源,因此需要credentials: 'include'
选项。frebpwbc2#
接受的答案对我不起作用,在
graphql-request
库的上下文中没有意义,它是可接受的参数,这是因为credentials
和mode
* 可以在header:
对象的内部和外部,这不会有任何区别(下面解释)。在本地测试OP的初始代码 * 没有任何更改 *,我没有看到任何问题。我看到Express
sessionID
从后端传递到前端。我确实看到了一个简单错误发生的可能性,尽管在开始和测试时...在您的代码中,当您使用
npm run dev
启动前端时,它会在127.0.0.1:7000
而不是localhost:7000
上启动应用。当您使用npm run start
启动后端时,它会在localhost:4000
上启动应用。在这种情况下,当您登录(或使用
saveUninitialized
true
)时,GraphQL
/Apollo Server
不会将Express
中的会话ID传递给客户端-因为您的域不同(127.0.0.1
与localhost
)。为了让GraphQL Express sessionID从后端cookie存储传播到前端cookie存储,您需要在本地使用相同的域。
例如,
sessionID
将使用以下组合正确传播:但是这里
sessionID
不会使用这些组合进行传播:这是很容易测试的,并且很明显是上述4种情况的情况。这让我想知道可接受的解决方案,因为它对我的OP代码的本地测试没有任何影响。
使用上面两个可接受的组合中的一个,我决定测试可接受的响应:
那么为什么它仍然对我有效呢?那是因为根据交集类型定义,
headers
可以存在于多个区域中,并且库将处理它们。右键单击上面代码中的
headers:
和VSCode
中的Go to Definition
时,将看到以下内容:注意
PatchedRequestInit
是一个交集类型吗?它有不止一个类型。它允许在不止一个地方设置header,而不是像公认的答案所建议的那样仅仅作为'header'的兄弟。最后,当我们检查
Dom.RequestInit
时,在交集的BOTH部分,我们看到相同的类型定义。在我广泛的测试中,我还验证了cookie需要为localhost设置
secure
false
,否则它不会从后端传播到前端。最后,我怀疑OP在多次尝试后,在某个点上手动正确访问了
localhost:7000
,因为他们的应用程序在127.0.0.1:7000
上自动启动有问题。然后他们尝试了将credentials
和mode
移出headers
的"其他解决方案"。所以我怀疑OP认为第二件事解决了他的问题,而实际上第一件事解决了。它这是唯一合乎逻辑的解释。除非OP能解释为什么他们的代码从www.example.com开始!127.0.0.1!基于我的研究,错误的答案被接受了,我承认我有点沮丧。我很高兴OP "让事情运转起来",但我希望未来的开发人员对"为什么"事情运转起来更感兴趣,慢慢地走,这样你就不会对正在发生的事情做出假设。
当在客户端:7000上并向服务器:4000提交一个空的登录表单以查看标题时,我看到
credentials: include
和mode: cors
,当这两个属性被放置在graphql-request
的headers:{}
块中时。这是repo最初的方式。因此,这些标题被正确地设置。当您执行接受的答案,然后重复登录表单提交时,这些标题消失了**。这意味着如何设置credentials
和mode
的原始文档示例似乎是错误的。如果它们不应该在标题块中,为什么它们在块内部时出现,然后在块外部时消失,正如文档和接受的答案所建议的那样?在这种情况下,眼见为实。我看到标题在以"错误"的方式进行设置时被设置。查看前后的标题,自己判断放置
credentials
和mode
属性的位置。无任何变更:
x759pob23#
我不完全确定,但有几件事:
首先,set-cookie不会在您的cookie中向请求主机显示should fall back域,但这可能取决于您的浏览器
其次,根据the MDN doc设置“/”路径,这将与“/graphql”路径不匹配。
最后,你的屏幕看起来像Firefox,但你说你在“应用程序-〉Cookie”中搜索你的Cookie,而在现代的Firefox中,它应该在“存储-〉Cookie”中搜索