如何在 React 中获取 HTTP-only cookie?

Xyp*_*riL 11 cookies node.js jwt reactjs redux

我目前正在开发一个 MERN 堆栈应用程序,我使用的身份验证是 JWT 并将其保存在我的 cookie 中。这就是我在用户登录后发送 cookie 的方式。

              res
                .cookie("token", token, {
                  httpOnly: true,
                  secure: true,
                  sameSite: "none",
                })
                .send();
Run Code Online (Sandbox Code Playgroud)

我通过在后端获取“令牌”cookie 来登录用户。然而,我用这个应用程序实现了 Redux,每次刷新页面时,它都会自动注销。我想要的是在我的前端(React)中检测浏览器中的“令牌”cookie,但我无法获取它。我尝试过使用 npm js-cookie 但仍然无法得到它。有没有办法获得“令牌”cookie?或者根据我读过的内容使用 redux-persist ?请帮忙,谢谢。

fab*_*ian 17

就像其他答案已经解释的那样,您无法通过 JS 访问 httpOnly cookie。

我个人建议您使用不同的方法。当然,cookie 和 httpOnly 听起来是个好主意,您可能会认为 cookie 比 localStorage 好一千倍,但最终,将令牌存储在 localStorage 还是 cookie 中并不重要。您可能会争论 cookie 与 localStorage 几个小时,但两者都有各自的漏洞(例如:cookie:CSRF-Attacks(https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html),localStorage:XSS)。

现在,虽然理论上您可以在这里使用 localStorage,但我并不提倡使用它。我建议您放弃 cookie 和 localStorage 并将 JWT 存储在您的应用程序状态中(无论是与 context-api、redux 等),然后发送带有身份验证标头的 JWT 以及您从前面发出的所有请求到后端。当然,您的后端需要验证该令牌。例如,您可以仅实现一个身份验证中间件,将其添加到所有需要身份验证的路由中。过期也非常简单,因为您不必再​​同步 JWT 和 cookie 的过期时间。只需在 JWT 上设置过期时间,身份验证中间件中该令牌的验证就会捕获该过期时间。如果您想知道为什么此方法可以安全地抵御 CSRF 攻击,请查看此处:Where to store JWT in browser? 如何防范CSRF?

这里有一些不错的文章,我真的建议您阅读第一篇文章: https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/ https://medium.com /@ryanchenkie_40935/react-authentication-how-to-store-jwt-in-a-cookie-346519310e81

编辑:为什么您可能不想使用 JWT 进行用户身份验证。

虽然 JWT 一直是用户身份验证的流行选择,但有几个因素有利于使用传统的仅 HTTP cookie 会话。

主要考虑因素:

  1. 如果攻击者获得 JWT,则没有简单的方法可以撤销他们的访问权限。相反,使用 cookie,可以使会话无效。虽然将 JWT 存储在 React 状态可能比使用 localStorage 提供一些安全性,但攻击者可能获得令牌访问权限的风险仍然存在。即使您选择将 JWT 存储在服务器端并执行验证,这也会抵消使用 JWT 的无状态优势,从而引发一个问题:为什么不简单地使用传统会话呢?

  2. JWT 本质上是为短期令牌而设计的,其生命周期通常为 5 分钟或更短。出于会话管理的目的,这个持续时间证明是不够的。此外,还存在与 JWT 相关的安全问题。您可以在这里阅读更多相关内容。

总之,无状态 JWT 缺乏失效或更新的能力,并且可能会带来安全问题,具体取决于其存储位置。相反,有状态 JWT 在功能上类似于会话 cookie,但可能缺乏经过验证和严格审查的实现以及强大的客户端支持。


phr*_*hry 7

你不能。“httpOnly”的意思是“JavaScript 无法访问它”。

使用 Redux-Persist 也不能​​真正帮助您确定您是否仍在登录或会​​话是否超时。该数据可能在几周前就已保留,或者令牌可能已被撤销。

您可以做的最明智的事情是在服务器上设置一个/whoami端点,并将其作为应用程序初始化向那里发送请求时的第一个操作。有关您的用户的信息返回 -> 很好,保存并显示它。否则,您会收到“401 未经授权”,这意味着用户未登录并且需要登录。


csa*_*nto 7

尽管您无法在前端使用 httpOnly cookie 执行任何操作,但肯定有一种方法可以处理前端发送的 httpOnly cookie 并从该 cookie 中提取 JWT,所有这些都在 MERN 堆栈应用程序的后端。

至于保留用户并防止“刷新时注销”问题,您将必须创建一个 useEffect 挂钩来不断检查令牌是否存在 - 我们稍后会讨论这一点。

首先,我建议在后端使用 Cors:

const cors = require("cors");

app.use(
  cors({
    origin: ["http://...firstOrigin...", ...],
    credentials: true,
  })
);
Run Code Online (Sandbox Code Playgroud)

准备就绪后,在创建 httpOnly cookie 时设置以下选项。另外,创建一个非 httpOnly cookie 来跟踪具有相同到期日期和布尔值的 httpOnly cookie,而不是 JWT。这将允许您使用“universal-cookie”库并实际读取前端中的非 httpOnly cookie:

              res
                .cookie("token", token, {
                  origin: "http://...firstOrigin..."
                  expires: // set desired expiration here
                  httpOnly: true,
                  secure: true,
                  sameSite: "none",
                })
                .cookie("checkToken", true, {
                  origin: "http://...firstOrigin..."
                  expires: // same as above
                  secure: true,
                  sameSite: "none",
                })

Run Code Online (Sandbox Code Playgroud)

创建了一个模仿我们实际“令牌”的“checkToken”cookie 后,我们可以使用它来设置状态(useState 挂钩)并通过 useEffect 挂钩保留用户(如果存在且未过期)。

然而,为了正确发送,我们必须首先指定一些事情。在这个例子中,我将使用 axios 在前端进行此类 API 调用。

请注意,每个 API 调用的请求标头都将包含我们的 httpOnly cookie 及其内容 - 我们可以通过打开 chrome 开发工具的网络选项卡来确认这一点,进行 API 调用,然后检查“请求标头”中的“Cookie”...

const cookies = new Cookies();
const checkToken = cookies.get("checkToken");

const AuthUser = () => {
  const [user, setUser] = useState(checkToken);

  useEffect(() => {
    async function checkToken() {
      await axios
        .post("http://...yourBackend.../authToken", {
          withCredentials: true,    // IMPORTANT!!!
        })
        .then((res) => {
          // handle response - if successful, set the state...
          // to persist the user
        )}
        .catch((err) => {
          // handle error
        )}
    };
  
    checkToken();
  }, []);

  // Implement your login behavior here
}

Run Code Online (Sandbox Code Playgroud)

完成后,我们可以确认我们在后端 API 调用的请求正文中获取了此类令牌(无论在何处处理),在控制台中记录 cookie 以检查它,然后将 cookie 的值存储在变量中启用对所述 cookie 的验证:

app.post(".../authToken", (req, res) => {
  // Get all cookies from request headers
  const { cookie } = req.headers;

  // Check to see if we got our cookies
  console.log(cookie);   
  
  // Handle this as you please
  if (cookie == undefined) return;
  
  const token = cookie.split("token=")[1].split(";")[0];   // Yep, it's a string
  console.log(token);    // Check to see if we stored our cookie's JWT

  // Some middleware:

  jwt.verify(token, process.env.TOKEN, (err, user) => {
    // if success upon verification,
    // issue new 'token' and 'checkToken'
  });
});
Run Code Online (Sandbox Code Playgroud)

完毕。

请注意,这是一个一般实现,仅作为了解 httpOnly cookie 功能的指南。OP从未提供过原始代码。

我希望这有帮助。祝你好运。


小智 6

在提高使用 React/Django 创建的项目的身份验证工作流程的安全性时,我们遇到了类似的问题

问题是:存储 JWT 的最佳位置是什么?

经过研究,我们最终实现了 Oauth2 协议,这里有一篇文章可以帮助您理解刷新令牌轮换的逻辑

我们的实施是

  1. 在后端生成 2 个令牌(访问令牌 [短寿命] 和刷新令牌 [长寿命])
  2. 刷新令牌应存储在 HttpOnly cookie 中(正如他们在响应中提到的,JS 无法在客户端访问它)
  3. 在前端级别,我们仅使用访问令牌,当它过期时,我们调用后端重新生成另一个访问和刷新
  4. 后端会访问HttpOnly cookie中的Refresh Token并决定是否有效生成新的Token
  5. 如果后端生成了新的有效token,则将Access Token发送给前端,并更新Cookie中的Refresh Token

Ps:按照这种逻辑,您无法访问前端的刷新令牌,因此当您的访问令牌不再有效时,您告诉服务器检查存储在 HttpOnly Cookie 中的刷新令牌是否仍然有效,然后重新生成其他有效令牌

我希望这能激励你