我必须将令牌存储在 cookie 或本地存储或会话中吗?

2022-01-10 00:00:00 jwt javascript

我正在使用 React SPA、Express、Express-session、Passport 和 JWT.我对一些用于存储令牌的不同客户端存储选项感到困惑:Cookie、Session 和 JWT/Passport.

I am using React SPA, Express, Express-session, Passport, and JWT. I'm confused about some of the different client-side storage options to store tokens: Cookies, Session, and JWT / Passport.

令牌是否必须存储在 cookie 中,即使我可以将它们存储在 req.sessionID 中?

Do tokens have to be stored in cookies, even if I can store them in req.sessionID?

许多网站使用 cookie 来存储购物车令牌.到目前为止,我已经根据会话 ID 存储了购物车数据,而没有添加任何 cookie.

Many websites use cookies to store shopping cart tokens. So far I have stored shopping cart data based on the session ID without adding any cookies.

所以当用户访问我的网站时,我会将其与他们的req.sessionID 然后检索数据库中的数据,如购物车和用户会话.

So when users visit my website, I will match it with their req.sessionID and then retrieve the data in the database like shopping carts and user session.

我需要存储 cookie 吗?我可以通过 req.sessionID 访问它以获取所需的数据.

Do I need to store cookies? I can access it via req.sessionID to get the data needed.

还有第二个

我已使用 passport-google-oauth20 进行身份验证.成功登录后,数据将保存到会话中.并将其发送给客户端,我必须通过 URL 查询 ?token='sdsaxas' 发送它.

I have made authentication using a passport-google-oauth20.After I successfully login, the data is saved into the session. and to send it to the client I have to send it via the URL query ?token='sdsaxas'.

在这种情况下,我有很多不同的意见.有人保存了它到本地存储中,有人通过使用 JWT 将其转换为令牌将其保存到 cookie 中.

in this case I get a lot of difference of opinion. someone saved it into local storage and someone saved it into cookies by converting it to a token using JWT.

 jwt.sign(
        payload,
        keys.jwt.secretOrPrivateKey, 
        {
            expiresIn:keys.jwt.expiresIn // < i dont know what is this expired for cookies or localstorage ?
        }, (err, token) => {

            res.redirect(keys.origin.url + "?token=" + token);
        });

我真的可以使用 sessionID(不带 cookie 或本地存储)存储与会话相关的所有内容吗?

因为我使用 React SPA,所以只需要进行一次或每次页面刷新并检索数据然后保存到 redux 中.

Only by doing fetch once or every page refresh and retrieving the data and then saved into redux because I use React SPA.

推荐答案

这个答案是基于无状态的方法,因此它没有谈论传统的会话管理

你问了两个完全不同的问题:

You have asked two altogether different questions:

  1. 购物车 - 与业务功能更相关
  2. OAuth 2 &JWT - 与安全和身份验证有关

作为电子商务网站的用户,我希望在上下班途中从移动设备添加到购物车中的任何商品在我从 PC 登录到网站后,都应该可以在购物车中找到到达家.因此,购物车数据应保存在后端数据库中并链接到我的用户帐户.

As an user of an ecommerce website, I'd expect that any item I add to my shopping cart from my mobile device while commuting to my workplace, should be available in the cart when I login to the website from my PC after reaching home. Therefore, the cart data should be saved in the back-end DB and linked to my user account.

在使用 OAuth 2.0 进行身份验证时,JWT 访问令牌和/或刷新令牌需要存储在客户端设备中的某个位置,这样一旦用户通过提供登录凭据进行身份验证,他就不需要提供他的凭据再次浏览该网站.在这种情况下,浏览器本地存储、会话存储和 cookie 都是有效的选项.但是,请注意这里的 cookie 没有链接到服务器端的任何会话.换句话说,cookie 不存储任何会话 ID.cookie 仅用作访问令牌的存储,该令牌随每个 http 请求传递给服务器,然后服务器使用数字签名验证令牌,以确保它没有被篡改并且没有过期.

When it comes to authentication using OAuth 2.0, the JWT access token and / or refresh token need to be stored somewhere in the client device, so that once the user authenticates himself by providing login credentials, he doesn't need to provide his credentials again to navigate through the website. In this context, the browser local storage, session storage and cookies are all valid options. However, note that here the cookie is not linked to any session on the server side. In other words, the cookie doesn't store any session id. The cookie is merely used as a storage for access token which is passed to the server with every http request and the server then validates the token using the digital signature to ensure that it is not tampered and it is not expired.

虽然访问和/或刷新令牌的所有三个存储选项都很流行,但如果以正确的方式使用,cookie 似乎是最安全的选项.

Although all three storage options for access and / or refresh tokens are popular, cookie seems to be the most secured option when used in the correct way.

为了更好地理解这一点,我建议您阅读 this 和 this 以及 OAuth 2.0 规范.

To understand this better, I recommend you read this and this along with the OAuth 2.0 specification.

我之前说过,cookie 似乎是最安全的选项.我想在这里进一步澄清这一点.

I said earlier that cookie seems to be the most secured options. I'd like to further clarify the point here.

我认为浏览器 localStoragesessionStorage 没有为存储身份验证令牌提供足够的安全性的原因如下:

The reason I think browser localStorage and sessionStorage do not provide enough security for storing auth tokens are as follows:

  1. 如果发生 XSS,恶意脚本可以轻松地从那里读取令牌并将其发送到远程服务器.从那里开始,远程服务器或攻击者在冒充受害者用户时不会有任何问题.

  1. If XSS occurs, the malicious script can easily read the tokens from there and send them to a remote server. There on-wards the remote server or attacker would have no problem in impersonating the victim user.

localStoragesessionStorage 不跨子域共享.因此,如果我们在不同的子域上运行两个 SPA,我们将无法获得 SSO 功能,因为一个应用程序存储的令牌将无法用于组织内的另一个应用程序.有一些使用 iframe 的解决方案,但这些看起来更像是变通方法,而不是一个好的解决方案.而当响应头 X-Frame-Options 用于避免使用 iframe 的点击劫持攻击时,任何使用 iframe 的解决方案都是没有问题的.

localStorage and sessionStorage are not shared across sub-domains. So, if we have two SPA running on different sub-domains, we won't get the SSO functionality because the token stored by one app won't be available to the other app within the organization. There are some solutions using iframe, but those look more like workarounds rather than a good solution. And when the response header X-Frame-Options is used to avoid clickjacking attacks with iframe, any solution with iframe is out of question.

但是,这些风险可以通过使用指纹来降低(如 OWASP JWT Cheat Sheet) 这又需要一个 cookie.

These risks can, however, be mitigated by using a fingerprint (as mentioned in OWASP JWT Cheat Sheet) which again in turn requires a cookie.

指纹的想法是,生成一个加密强的随机字节串.然后将原始字符串的 Base64 字符串存储在 HttpOnlySecureSameSite cookie 中,名称前缀为 __Secure-.应根据业务需求使用正确的域和路径属性值.字符串的 SHA256 哈希也将在 JWT 的声明中传递.因此,即使 XSS 攻击将 JWT 访问令牌发送到攻击者控制的远程服务器,它也无法在 cookie 中发送原始字符串,因此服务器可以根据 cookie 的缺失拒绝请求.XSS 脚本无法读取 HttpOnly 的 cookie.

The idea of fingerprint is, generate a cryptographically strong random string of bytes. The Base64 string of the raw string will then be stored in a HttpOnly, Secure, SameSite cookie with name prefix __Secure-. Proper values for Domain and Path attributes should be used as per business requirement. A SHA256 hash of the string will also be passed in a claim of JWT. Thus even if an XSS attack sends the JWT access token to an attacker controlled remote server, it cannot send the original string in cookie and as a result the server can reject the request based on the absence of the cookie. The cookie being HttpOnly cannot be read by XSS scripts.

因此,即使我们使用 localStoragesessionStorage,我们也必须使用 cookie 来确保其安全.最重要的是,我们添加了上面提到的子域限制.

Therefore, even when we use localStorage and sessionStorage, we have to use a cookie to make it secured. On top of that, we add the sub-domain restriction as mentioned above.

现在,使用 cookie 存储 JWT 的唯一问题是 CSRF 攻击.由于我们使用 SameSite cookie,CSRF 得到缓解,因为跨站点请求(AJAX 或仅通过超链接)是不可能的.如果该站点用于任何旧浏览器或其他一些不那么流行的不支持 SameSite cookie 的浏览器,我们仍然可以通过另外使用具有加密强随机值的 CSRF cookie 来缓解 CSRF,这样每个AJAX 请求读取 cookie 值并将 cookie 值添加到自定义 HTTP 标头中(GET 和 HEAD 请求除外,它们不应该进行任何状态修改).由于 CSRF 由于同源策略而无法读取任何内容,并且它基于利用 POST、PUT 和 DELETE 等不安全的 HTTP 方法,因此此 CSRF cookie 将减轻 CSRF 风险.所有现代 SPA 框架都使用这种使用 CSRF cookie 的方法.这里提到了 Angular 方法.

Now, the only concern about using a cookie to store JWT is, CSRF attack. Since we use SameSite cookie, CSRF is mitigated because cross-site requests (AJAX or just through hyperlinks) are not possible. If the site is used in any old browser or some other not so popular browsers that do not support SameSite cookie, we can still mitigate CSRF by additionally using a CSRF cookie with a cryptographically strong random value such that every AJAX request reads the cookie value and add the cookie value in a custom HTTP header (except GET and HEAD requests which are not supposed to do any state modifications). Since CSRF cannot read anything due to same origin policy and it is based on exploiting the unsafe HTTP methods like POST, PUT and DELETE, this CSRF cookie will mitigate the CSRF risk. This approach of using CSRF cookie is used by all modern SPA frameworks. The Angular approach is mentioned here.

另外,由于 cookie 是 httpOnlySecured,XSS 脚本无法读取它.因此 XSS 也得到了缓解.

Also, since the cookie is httpOnly and Secured, XSS script cannot read it. Thus XSS is also mitigated.

值得一提的是,可以通过使用适当的 content-security-policy 响应标头进一步缓解 XSS 和脚本注入.

It may be also worth mentioning that XSS and script injection can be further mitigated by using appropriate content-security-policy response header.

  1. 状态变量(Auth0 使用它)- 客户端将生成并随每个请求传递一个加密的强随机随机数,服务器将连同其响应一起回显该随机数,从而允许客户端验证随机数.Auth0 文档中对此进行了说明.
  2. 始终检查referer 标头并仅在referer 是受信任的域时接受请求.如果没有引用标头或未列入白名单的域,则只需拒绝请求.使用 SSL/TLS 时,通常会出现引用.登陆页面(主要是信息性的,不包含登录表单或任何安全内容)可能有点放松,并允许缺少引用标头的请求.
  3. 应在服务器中阻止 TRACE HTTP 方法,因为这可用于读取 httpOnly cookie.
  4. 另外,设置标题 Strict-Transport-Security: max-age=;includeSubDomains 只允许安全连接,以防止任何中间人覆盖子域中的 CSRF cookie.
  1. State Variable (Auth0 uses it) - The client will generate and pass with every request a cryptographically strong random nonce which the server will echo back along with its response allowing the client to validate the nonce. It's explained in Auth0 doc.
  2. Always check the referer header and accept requests only when referer is a trusted domain. If referer header is absent or a non-whitelisted domain, simply reject the request. When using SSL/TLS referrer is usually present. Landing pages (that is mostly informational and not containing login form or any secured content) may be little relaxed ​and allow requests with missing referer header.
  3. TRACE HTTP method should be blocked in the server as this can be used to read the httpOnly cookie.
  4. Also, set the header Strict-Transport-Security: max-age=; includeSubDomains​ to allow only secured connections to prevent any man-in-the-middle overwrite the CSRF cookies from a sub-domain.

相关文章