Apollo GraphQL无法在浏览器存储Cookie导致鉴权失效
问题由三处配置/逻辑错误共同导致,按以下步骤修改即可恢复正常:
1. 修正反向代理信任配置
当前trust proxy配置逻辑写反:
// 原错误写法 app.set('trust proxy', !__prod__)
该配置仅在生产环境服务部署在Nginx、云负载均衡等反向代理后时需要开启,开发环境无需开启,修改为:
app.set('trust proxy', __prod__)
2. 修正Cookie安全属性不匹配问题
当前Cookie配置违反浏览器安全规则:设置sameSite: "none"允许跨域携带Cookie的同时,开发环境下secure属性为false。浏览器强制要求:SameSite值为none时,必须同时开启Secure属性(仅允许HTTPS环境传输Cookie),否则会直接拦截Set-Cookie响应头,不会将Cookie写入浏览器存储。
分场景调整配置:
- 本地HTTP开发调试:将
sameSite改为"lax",secure保持false即可,适配本地同域请求 - 需要在Apollo Studio(HTTPS跨域环境)调试:本地用mkcert等工具给本地服务签发HTTPS证书,启动HTTPS服务后保持
sameSite: "none",同时将secure设为true - 生产环境HTTPS部署:保持
sameSite: "none"、secure: true即可
参考配置(开发环境):
cookie: { maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10年有效期 httpOnly: true, sameSite: "lax", secure: false, },
同时确认Apollo Studio的连接配置中已开启「Include cookies」选项,允许跨域请求携带凭证。
3. 修复会话写入时序问题
当前login逻辑中给req.session.userId赋值后直接返回响应,没有等待Redis完成会话持久化,存在时序差:响应可能在会话数据写入Redis前就发送给客户端,导致后续请求携带的sessionId在Redis中查不到对应数据,依赖会话的me查询自然返回null。
修改login方法,等待会话保存完成后再返回响应:
@Mutation(() => UserResponse) async login( @Arg("options") options: UsernamePasswordInput, @Ctx() { em,req }: MyContext ): Promise<UserResponse> { // 原有用户名校验、密码校验逻辑保持不变 const valid = await argon2.verify(user.password, options.password); if (!valid) { return { errors: [ { field: "password", message: "incorrect password", }, ], }; } req.session.userId = user.id; // 等待会话写入Redis完成 await new Promise((resolve, reject) => { req.session.save((err) => { if (err) return reject(err); resolve(true); }) }) return { user }; }
如果需要注册完成后自动登录,给register方法也加上相同的会话保存逻辑即可。
验证方式
修改完成重启服务后,在浏览器开发者工具的网络面板中查看login请求的响应头,确认存在Set-Cookie: qid=xxx字段,且控制台无Cookie拦截相关的安全报错,即可正常使用会话功能。
内容的提问来源于stack exchange,提问作者solomon mulatu

