在 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"));
可用选项和限制:
有两种类型的选项可用于存储您的令牌:
- Web Storage API:它提供了 2 种机制:
sessionStorage
和 localStorage
。存储在此处的数据将始终可供您的 Javascript 代码使用,并且无法从后端访问。因此,您必须手动将其添加到 header 中的请求中。此存储仅适用于您应用的域,不适用于子域。这两种机制的主要区别在于数据过期:
sessionStorage
:数据仅适用于 session(直到浏览器或标签页关闭)。
localStorage
:存储数据没有过期时间,只能通过JavaScript清除,或者清除浏览器cache/Locally存储数据
- 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 令牌后,我们的想法是:
- 将
header.payload
部分存储在 SameSite
Secure
Cookie 中(因此只能通过 https 访问并且仍然可用于 JS 代码)
- 将
signature
部分存储在 SameSite
Secure
HttpOnly
Cookie 中
- 在您的后端实施一个中间件,以从这 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 对游戏来说是相当新的(几年了,但不像开发中的其他主题那么成熟)。找到一个可行的解决方案需要花费一些时间和精力,但让我们继续构建安全的软件,而不是让网络上泛滥成灾。
最好,编码愉快。
我正在用 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"));
可用选项和限制:
有两种类型的选项可用于存储您的令牌:
- Web Storage API:它提供了 2 种机制:
sessionStorage
和localStorage
。存储在此处的数据将始终可供您的 Javascript 代码使用,并且无法从后端访问。因此,您必须手动将其添加到 header 中的请求中。此存储仅适用于您应用的域,不适用于子域。这两种机制的主要区别在于数据过期:
sessionStorage
:数据仅适用于 session(直到浏览器或标签页关闭)。localStorage
:存储数据没有过期时间,只能通过JavaScript清除,或者清除浏览器cache/Locally存储数据
- 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 令牌后,我们的想法是:
- 将
header.payload
部分存储在SameSite
Secure
Cookie 中(因此只能通过 https 访问并且仍然可用于 JS 代码) - 将
signature
部分存储在SameSite
Secure
HttpOnly
Cookie 中
- 在您的后端实施一个中间件,以从这 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 对游戏来说是相当新的(几年了,但不像开发中的其他主题那么成熟)。找到一个可行的解决方案需要花费一些时间和精力,但让我们继续构建安全的软件,而不是让网络上泛滥成灾。
最好,编码愉快。