在 react.js 中存储访问令牌的位置?

Posted

技术标签:

【中文标题】在 react.js 中存储访问令牌的位置?【英文标题】:Where to store access-token in react.js? 【发布时间】:2018-08-05 15:01:59 【问题描述】:

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

【问题讨论】:

你在使用 redux 吗? 你可以把它绑定到window对象上,也可以把它放到你的redux store的初始状态。 但在刷新时,access_token 将为空或未定义! @codebased,我使用firebase 存储access-token 我认为 localStorage 完全没问题。如果您担心,请使用一些简单的身份验证令牌(JWT 不能过期)并给它一个短暂的过期时间,例如 1 小时。另外,如果您想保护一些关键操作(例如付款或某事),请设置这些密码。 【参考方案1】:

可用选项和限制:

有两种存储令牌的选项:

    Web Storage API:提供 2 种机制:sessionStoragelocalStorage。此处存储的数据将始终可供您的 javascript 代码使用,并且无法从后端访问。因此,例如,您必须手动将其添加到您的请求中。此存储仅适用于您应用的域,而不适用于子域。这两种机制的主要区别在于数据过期:
sessionStorage:数据仅可用于会话(直到浏览器或选项卡关闭)。 localStorage: 存储没有过期日期的数据,只能通过 JavaScript 清除,或者清除浏览器缓存/本地存储的数据
    Cookies:随后续请求自动发送到您的后端。可以控制 Javascript 代码的到期和可见性。可用于您应用的子域。

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

安全性:访问或身份令牌是敏感信息。要始终考虑的主要攻击类型是 Cross Site Scripting (XSS) 和 Cross Site Request Forgery (CSRF)。 功能要求:关闭浏览器时用户是否应该保持登录状态?他的会议将持续多长时间?等等

出于安全考虑,OWASP 不建议将敏感数据存储在 Web 存储中。您可以查看他们的CheatSheetSeries 页面。你也可以阅读this detailed article了解更多详情。

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

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

建议的解决方案:

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

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

通常有用的信息存在于有效负载中,例如用户角色(可用于调整/隐藏部分 UI)。因此,保持该部分可用于 Javascript 代码非常重要。

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

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

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

Peter Locke 在this article 中提出并很好地描述了这个想法。

【讨论】:

那么在第 3 步中,你将如何从浏览器中提取 cookie 并将其附加到标题中? 第 3 步用于后端服务。它将收到 2 个单独的 cookie。因此,与其读取一个 cookie,不如检查其中的 2 个。此步骤不涉及浏览器。 我明白了。但是,您必须将 cookie 传递到后端。如果使用 axios,如何在 ui 中提取一个 httpOnly cookie,将其附加到 Authorization 标头?目前,我可以简单地使用withCredentials: true 以常规headers.cookies 发送cookie,但我不知道如何在授权标头中发送它们 您不会在 Authorization 标头中发送它们。 HttpOnly cookie 对您的 Javascript 代码不可见(这就是这种方法的目标)。在您的后端中间件中,您将收到 2 个 cookie 并将它们合并到 Authorization 标头中。 所以你的意思是你将后端的cookie合并到授权头之前它们可以被各种控制器使用?【参考方案2】:

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

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

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

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

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

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

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

最好的,快乐的编码。

【讨论】:

惊人的答案!谢谢兰明 :) 很棒的遮阳篷正是我需要听到的 laravel 后端,护照已经使用 refresh_tokens,我不知道它们的用途。 很好的答案,但您不应该将刷新令牌存储在本地或会话存储中。服务器是唯一需要读取刷新令牌的服务器,您可以通过将其存储在 HttpOnly 并将安全设置为 true 的 cookie 中来做到这一点 我同意 Udendu Abasili。我的答案集中在 100% 无状态的方法上。这就是为什么我决定将加密令牌存储在本地或会话 cookie 中。但是,如果可能,我通常也建议使用(加密的)HttpOnly cookie。 我的意思是“本地或会话存储”,很明显。刚刚意识到我在前面的评论中犯了这个错误。【参考方案3】:

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

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

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"));

【讨论】:

像这样存储令牌是一个安全问题。 @Darbio 为什么这是一个安全问题,否则你怎么做? 查看此链接以获取有关为什么这是一个坏主意的更多信息:github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/… 另一种方法是设置一个只有服务器才能读取的安全、仅限 http 的 cookie。此 cookie 随每个请求一起发送,无法使用 javascript 读取。 如果没有后端可以接受吗?显然不建议使用 accessTokens。但是,如果客户端直接调用 API,这似乎是唯一的方法?

以上是关于在 react.js 中存储访问令牌的位置?的主要内容,如果未能解决你的问题,请参考以下文章

存储位置 - OAuth 2.0 中的访问令牌和刷新令牌

我可以在哪里将 JWT 令牌存储在 React js 中以验证应用程序后端的各种路由?

访问令牌和刷新令牌的存储位置和方式

在 .net 核心 web api 中存储 JWT 令牌的位置?

无法在 React.js 中读取 json,出现意外的令牌错误

Node.js 护照 OAuth 2.0 身份验证:存储访问和刷新令牌的位置