You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

HttpOnly Cookie已随响应发送但浏览器未存储(NestJS+NextJS Apollo)

HttpOnly Cookie已随后端响应发送,但浏览器Application>Cookies中无法找到

问题现象

HttpOnly Cookie已通过后端NestJS服务的响应发送,但在浏览器开发者工具的Application>Cookies面板中无法找到该Cookie。

后端NestJS登录接口代码

async loginSeller(loginSellerInput: LoginSellerInput, ctx: any) {
    const user = await this.authService.validateUser(
      loginSellerInput.email,
      loginSellerInput.password,
    );
    if (!user) {
      throw new BadRequestException(`Email or password are invalid`);
    } else {
      const seller = await this.sellerModel.findOne({
        email: loginSellerInput.email,
      });
      seller.last_connected = new Date();
      seller.save();
      const tokens = await this.authService.generateUserCredentials(user);
      const serialised = serialize('OursiteJWT', tokens.access_token, {
        httpOnly: true,
        secure: process.env.NODE_ENV !== 'development',
        sameSite: 'strict',
        maxAge: 60 * 60 * 24 * 30,
        path: '/',
      });
      await this.usersService.updateRtHash(user.id, tokens.refresh_token);
      return tokens;
    }
  }

前端NextJS Apollo客户端配置

import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client";
import { setContext } from "@apollo/client/link/context";

const httpLink = createHttpLink({
    uri: "http://localhost:3000/graphql",
    credentials: "same-origin",
});

const authLink = setContext((_, { headers }) => {
    // get the authentication token from local storage if it exists
    // const token = localStorage.getItem("token");
    // return the headers to the context so httpLink can read them
    return {
        headers: {
            ...headers,
            // authorization: token ? `Bearer ${token}` : "",
        },
    };
});

const client = new ApolloClient({
    link: authLink.concat(httpLink),
    cache: new InMemoryCache(),
});

// const client = new ApolloClient({
//  uri: "http://localhost:3000/graphql",
//  cache: new InMemoryCache(),
// });

export default client;

已确认信息

  • Cookie确实已通过后端响应发送(可通过网络请求的响应头验证)
  • 浏览器端Application>Cookies中无该Cookie显示

已尝试方案

  • 在NestJS的main.ts中启用CORS配置
  • 修改Apollo客户端的相关配置,但问题未解决

问题排查与解决方案

1. 核心问题:未将序列化Cookie添加到响应头

当前代码仅生成了Cookie的序列化字符串,但没有将其注入到响应头中。在NestJS中,需要通过ctx对象获取响应实例,手动设置Set-Cookie头:

// 在return tokens之前添加以下代码
ctx.res.setHeader('Set-Cookie', serialised);

这是浏览器接收不到Cookie的最直接原因。

2. 修正CORS配置

确保NestJS的CORS配置允许携带凭证,且Origin与前端地址完全匹配。在main.ts中配置:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors({
    origin: 'http://localhost:3001', // 替换为你的前端实际运行地址
    credentials: true, // 必须设为true,允许跨域携带Cookie
  });
  await app.listen(3000);
}
bootstrap();

3. 调整Apollo客户端的凭证配置

如果前后端运行在不同端口(如后端3000、前端3001),same-origin会导致凭证无法正常传递,需改为include:

const httpLink = createHttpLink({
    uri: "http://localhost:3000/graphql",
    credentials: "include", // 跨域场景下使用include
});

4. 验证开发环境下的secure属性

开发环境通常使用HTTP协议,此时secure: true会被浏览器拒绝存储。当前代码中secure: process.env.NODE_ENV !== 'development'的逻辑是正确的,但需确保NODE_ENV环境变量确实被设置为development。

5. 补充Cookie的Domain属性(跨端口场景)

若前后端同域名但不同端口,可指定domain为localhost(不要加端口),确保Cookie能被共享:

const serialised = serialize('OursiteJWT', tokens.access_token, {
  httpOnly: true,
  secure: process.env.NODE_ENV !== 'development',
  sameSite: 'lax', // 严格模式下跨域可能受限,可先改为lax测试
  maxAge: 60 * 60 * 24 * 30,
  path: '/',
  domain: 'localhost', // 添加该属性
});

内容的提问来源于stack exchange,提问作者Aymane Elmadidi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 18:55:28