在 react.js 中的何处存储访问令牌?

Where to store access-token in react.js?

我正在用 Reactjs 构建一个应用程序。在验证 access_token 之后,我必须进行提取调用。注册时,access_token 是从后端服务器获取的。但是,在哪里存储这些 access_token。有什么方法可以使这些 access_token 全局化,以便所有组件都可以访问它。我使用过本地存储、缓存和会话存储,但这些都是不可取的。 过去几天一直在这个问题上搁置,任何解决方案。提前致谢。

Michael Washburn 有一篇关于如何使用 redux 保持状态的非常好的文章,here on his webpage

在文章中,他有一个 link 到 very descriptive video tutorial 由 Redux 的合著者之一 Dan Abramov 创建,我跟着他一起将其添加到我的项目中。 这是我用来让它工作的代码:

store.js

import { createStore, combineReducers } from "redux";
import { UserReducer, CopyReducer } from "../reducers";
import { loadState, saveState } from "../utils/localStorage";

export const giveMeStore = () => {
  const reducers = combineReducers({
    copy: CopyReducer,
    user: UserReducer
  });
  const persistedState = loadState();
  const store = createStore(reducers, persistedState);
  //user contains the TOKEN
  store.subscribe(() => {
    saveState({
      user: store.getState().user
    });
  });
  return store;
};

localStorage.js

export const loadState = () => {
  try {
    const serializedState = localStorage.getItem("state");
    if (serializedState === null) {
      return undefined;
    }
    return JSON.parse(serializedState);
  } catch (err) {
    return undefined;
  }
};
export const saveState = state => {
  try {
    const serializedState = JSON.stringify(state);
    localStorage.setItem("state", serializedState);
  } catch (err) {
    //ignoring write erros
  }
};

并将商店添加到提供商:

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";

import { giveMeStore } from "./store.js";

const Root = () => {
  return (
    <Provider store={giveMeStore()}>
      //... your components
      //...
    </Provider>
  );
};

ReactDOM.render(<Root />, document.querySelector("#root"));

可用选项和限制:

有两种类型的选项可用于存储您的令牌:

  1. Web Storage API:它提供了 2 种机制:sessionStoragelocalStorage。存储在此处的数据将始终可供您的 Javascript 代码使用,并且无法从后端访问。因此,您必须手动将其添加到 header 中的请求中。此存储仅适用于您应用的域,不适用于子域。这两种机制的主要区别在于数据过期:
  • sessionStorage:数据仅适用于 session(直到浏览器或标签页关闭)。
  • localStorage:存储数据没有过期时间,只能通过JavaScript清除,或者清除浏览器cache/Locally存储数据
  1. Cookies:随后续请求自动发送到您的后端。可以控制 Javascript 代码的到期和可见性。可用于您应用的子域。

您在设计身份验证机制时必须考虑两个方面:

  • 安全性:访问或身份令牌是敏感信息。要始终考虑的主要攻击类型是 Cross Site Scripting (XSS) and Cross Site Request Forgery (CSRF)。
  • 功能需求:关闭浏览器后用户是否应该保持登录状态?他的session还有多久?等等

出于安全考虑,OWASP does not recommend storing sensitive data in a Web Storage. You can check their CheatSheetSeries page. You can also read this detailed article了解更多详情。

原因主要与XSS漏洞有关。如果您的前端不是 100% 防止 XSS 攻击,那么恶意代码可以在您的网页中执行,并且它可以访问令牌。 完全 XSS-proof 非常困难,因为它可能是由您使用的 Javascript 库之一引起的。

另一方面,如果将 Cookie 设置为 HttpOnly,则 Javascript 无法访问它们。 现在 cookie 的问题是它们很容易使您的网站容易受到 CSRF 的攻击。 SameSite cookie 可以减轻此类攻击。但是,旧版本的浏览器不支持这种类型的 cookie,因此可以使用其他方法,例如使用状态变量。在此 Auth0 文档 article.

中有详细说明

建议的解决方案:

为了安全地存储您的令牌,我建议您使用如下所述的 2 个 cookie 的组合:

JWT 令牌具有以下结构:header.payload.signature

通常有效载荷中存在有用的信息,例如用户角色(可用于 UI 的 adapt/hide 部分)。因此,让 Javascript 代码可以使用该部分非常重要。

身份验证流程完成并在后端创建 JWT 令牌后,我们的想法是:

  1. header.payload 部分存储在 SameSite Secure Cookie 中(因此只能通过 https 访问并且仍然可用于 JS 代码)
  2. signature 部分存储在 SameSite Secure HttpOnly Cookie
  3. 在您的后端实施一个中间件,以从这 2 个 cookie 中重建 JWT 令牌并将其放入 header:Authorization: Bearer your_token

您可以设置 cookie 的过期时间以满足您应用的要求。

Peter Locke 在 this article 中提出并详细描述了这个想法。

虽然来晚了,但我想分享一下我对这个话题的看法。 Anouar 给出了很好的答案,包括被认为是针对 XSS 保存的 http-only cookie,指出了 CSRF 漏洞并链接了 Peter Locke 的文章。

但是,就我而言,我需要应用程序是 100% 无状态的,这意味着我不能使用 cookie。

从安全的角度来看,将访问令牌存储在持久位置(如 localStorage,window,..)是一种不好的做法。因此,您可以使用 redux(或 state/context 中内置的 react.js)将 JWT 存储在变量中。这将保护令牌免受上述攻击,但在刷新页面后将其清空。

我解决这个问题的方法是使用刷新令牌,我将其存储在 localStorage 中(如果您愿意,可以使用会话存储或类似存储)。该刷新令牌的唯一目的是获取新的访问令牌,并且后端确保刷新令牌没有被盗(例如,实现一个被检查的计数器)。 我将访问令牌保存在缓存中(我的应用程序中的一个变量),一旦过期或由于重新加载而丢失,我使用刷新令牌来获取新的访问令牌。

显然,这仅在您同时构建后端(或至少后端实现刷新令牌)时才有效。如果您处理现有的 API 未实现刷新令牌等,并且将访问令牌保存在变量中不是您的选择(由于重新加载时为空),您还可以使用加密令牌在将应用程序机密保存到 localStorage(或会话存储,或者......是的,你明白了)之前。请注意,解密令牌需要一些时间,并且会降低您的应用程序的速度。因此,您可以将加密令牌保存到 localStorage(或...),并在刷新后仅解密一次,然后将其保存在 state/redux 变量中,直到您再次从 localStorage 刷新 again/decrypt 等。

关于这个话题的最后一句话:Auth 是应用程序的关键基础设施,尽管有趣的游戏和在线银行之间存在明显的区别(您可能想对银行“偏执”,而只是“担心”游戏),诸如“localStorage 完全没问题”或“在最坏的情况下会发生什么?1 小时后过期”之类的回答是危险的,而且完全是错误的。机器可以在几秒钟内造成大量伤害,你不想让这个差距敞开。如果您懒得保护您的应用程序,也许您想使用现有解决方案而不是构建自己的解决方案。

也就是说,JWT / token auth 对游戏来说是相当新的(几年了,但不像开发中的其他主题那么成熟)。找到一个可行的解决方案需要花费一些时间和精力,但让我们继续构建安全的软件,而不是让网络上泛滥成灾。

最好,编码愉快。