Cookie 未在 NodeJS 和 NextJS 的请求中设置

我正在使用 Node + Express 后端和 NextJS 前端(单独的服务器)开发一个全栈应用程序,并且在请求浏览器附加作为来自node服务器的响应标头的一部分出售的 cookie 时遇到问题。这是设置:

  1. node服务器运行在 localhost:3000,NextJs 服务器运行在 localhost:3001。
  2. 我在 etc/hosts 中设置了别名,将 someAlias.com 路由到 127.0.0.1。

使用前端用户界面(端口 3001),我能够使用 JsHttp 的 cookie 模块从后端(端口 3000)使用以下代码出售 cookie:

import { serialize } from 'cookie';
...
const cookie = serialize(TOKEN_NAME, TOKEN_VAL, {
  httpOnly: true,
  sameSite: 'none',
});

我能够在响应中观察到 Set-Cookie 标头。

但是,在随后的请求中,我没有看到附加的 cookie。我试过摆弄上面的 cookie 序列化参数但没有成功:

以下是我尝试过的论点:

  • domain: ['.someAlias.com:3000', '.someAlias.com:3001']
  • path: '/'
  • domain: '.someAlias.com'

我有一种感觉,这可能只是由于前端和后端服务器端口不同,但所有请求都在客户端发起,转到 localhost:3000(后端端口)。所以不确定我在这里可能做错了什么。

====== 更新 =======

我又进行了几次实验,发现当我直接访问 URL 时,NextJs 会在服务器端呈现页面。当我在应用程序内的页面之间转换时,页面呈现在客户端,它直接查询后端端口 3000。不幸的是,在这两种情况下,我都没有看到任何 cookie 被设置...

stack overflow Cookie not set in request with NodeJS and NextJS
原文答案

答案:

作者头像

同一站点无

如果您使用 SameSite=none ,则还需要使用 Secure ,这意味着还必须使用 SSL。然后您说您的 2 个服务器来自不相关的域,这可能不是您想要的,因为浏览器会积极丢弃 cookie。

本地 PC 选项

最初在您的开发计算机上使用这些设置,并确保浏览器和 Ajax 调用中使用的所有 URL 都使用 http://somealias.com:3000http://somealias.com:3001 而不是 localhost。然后 Cookie 将粘贴在开发计算机上。

  • 域=.somealias.com
  • 路径=/
  • 相同站点=严格
  • 仅限 HTTP

已部署的选项

当您部署到适当的环境时,还要使用 SSL 并设置 cookie 选项 Secure 。最重要的是,这两个域必须满足共享同一基本域的托管先决条件,例如:

这可确保发出的 cookie 被视为第一方且位于同一站点中,因此它们不会被丢弃。如果不满足先决条件,则您无法在代码中执行任何操作来解决问题。

类似资源

这个 Curity code example 使用本地开发域和相同的站点 cookie 设置,类似于我在上面使用的设置,可能有助于比较。

作者头像

cookie必须直接从浏览器发送到服务器,当您使用NextJ时,情况并非如此。因为当您访问应用程序时,下一个js将呈现您的页面,然后将请求从NextJS服务器发送到您的Nodejs服务器,因此浏览器将将Cookie发送到NextJS服务器,而不是NOT nodejs Server。解决方案是将Cookie手动从NextJS服务器手动发送到Nodejs。fetchapi和getServersideProps函数的示例:

export async function getServerSideProps(context){ 
  try{
       const res = await fetch(`your-api-endpoint`, {
          method: 'GET',
          credentials:'include',
          headers: {
             'Access-Control-Allow-Credentials': true,
             Cookie: context.req.headers.cookie
          },
    })
    const data = await res.json()
    if(!res.ok){
       throw data 
    }
    }catch(err){
       return console.log(err)
    }

    return {
      props: {
         data
      }
    }
}
作者头像

您应该使用 res.set Express 方法设置序列化 cookie。

或者,您可以使用 res.cookie 方法而无需额外的 cookie 包,如下所示:

res.cookie(TOKEN_NAME, TOKEN_VAL, {
  httpOnly: true,
  sameSite: 'none',
});

请注意,您不必担心同一域上的不同端口,因为 cookie 不是按端口隔离的,而是仅按域隔离的。无论您使用什么端口,cookie 都应该是可见的。

作者头像

既然您说:“我能够在响应中观察set-cookie标题”,我相信您的node.js设置正确。

当您从nodeJS获得响应时,您需要设置cookie,可以轻松使用NPM软件包完成。我将用 js-cookie 演示:

 import Cookies from "js-cookie";

您编写可重复使用的功能来设置cookie:

// what ever your project returns 
setSession(authResult) {
    //converting everything to miliseconds
    const expiresAt =
      JSON.stringify(authResult.expiresIn * 1000) + new Date().getTime();
    // I just put  properties. I dont know how project sets
    Cookies.set("user", authResult.idTokenPayload);
    Cookies.set("jwt", authResult.idToken);
    Cookies.set("expiresAt", expiresAt);
  }

每次提出要求时,都必须设置标题。如果您在浏览器上或服务器上,则必须根据cookie检索cookie。因此,如果您在服务器上,则必须编写功能。由于我演示了如何使用 js-cookies 设置cookie,因此您可以轻松地在浏览器上获取cookie。如果您在服务器上,则可重复使用的功能可以检索cookie:

// cookieKey: I set three "user", "jwt","expiresAt"
export const getCookieFromReq = (req, cookieKey) => {
  console.log("req.headers", req.headers);
  // cookies are attached to the req.header.cookie. 
  const cookie = req.headers.cookie
    .split(";")
    .find((c) => c.trim().startsWith(`${cookieKey}=`));

  if (!cookie) return undefined;
  return cookie.split("=")[1];
};

现在,您必须编写一个函数来设置标题:

import Cookies from "js-cookie";
import { getCookieFromReq } from "./directoryOf";

export const setAuthHeader = (req) => {
  const token = req ? getCookieFromReq(req, "jwt") : Cookies.getJSON("jwt");

  if (token) {
    return {
      headers: { authorization: `Bearer ${token}` },
    };
  }
  return undefined;
};

现在,当您提出请求时,必须使用此 setAuthHeader 。例如:

 await axiosInstance
    .post("/blogs", blogData, setAuthHeader())
    .then((response) => response.data)
    .catch((error) => rejectPromise(error));