Rethinking About Cookies and CORS

Written byKalanKalan
💡

If you have any questions or feedback, pleasefill out this form

This post is translated by ChatGPT and originally written in Mandarin, so there may be some inaccuracies or mistakes.

Introduction

In 2019, I published an article on Medium about Cookies and CORS (Dealing with Cookies and CORS), which received a good response, and the article was even copied across the strait. I hope everyone remembers the original author here. The article explored what CORS is and how to correctly implement it on both the frontend and backend, a common challenge in web development.

More than a year later, Chrome announced a change in Cookie policy (defaulting SameSite to lax), prompting me to rethink this topic and share my reflections in the article Chrome Cookie Policy Adjustments and Reflections, where I expressed my queries regarding SameSite. There are two questions I am still seeking answers to:

  1. Once the Server adds SameSite, is there still a need to implement CSRF mechanisms? If users with older browsers (which do not support SameSite Attribute) encounter security issues, should this security liability fall on the company or the browser?
  2. Is using Cookies truly a foolproof method?

Regarding the first question, I believe that as time progresses, more browsers will catch up, and at that point, CSRF mechanisms could potentially be completely phased out.

The second question arose from observing a few cases, prompting me to elaborate further:

Before we dive in, here’s a quick test: What is the scope of setting domain=.example.com versus domain=example.com in cookies?

We’ll revisit the answer shortly; first, let’s examine a classic case - Cookie Bomb.

In the article, GitHub Pages is used to demonstrate the Cookie Bomb.

GitHub allows not only code hosting but also the creation of GitHub Pages for personal websites or blogs, providing you with a user.github.io domain name. (Earlier, it was directly username.github.com, as mentioned in this article.)

In general browsers, a single Cookie (including key, value, expires, etc.) can store up to 4K of data, with a maximum of 50 cookies (this may vary slightly by browser), totaling up to 4KB * 50 = 200KB. The Cookie Bomb technique involves writing a large number of Cookies on the client side using document.cookie. Since cookies are included in all requests by default, 200KB is substantial for a GET request, which is why such requests are typically blocked at the server or nginx level.

bomb

Herein lies the biggest problem: Cookies automatically apply to all subdomains (with certain exceptions), so when you fall victim on page A (overloaded with Cookies), the effect also carries over to pages B and C. This results in all GitHub Pages appearing broken.

Strictly speaking, this Cookie Bomb does not pose a security issue; it’s merely a quirky prank. The victim can simply delete the excessive Cookies to have the requests processed correctly by the Server. However, the idea of shared Cookies across such blog services seems odd.

To mitigate this issue, certain subdomains within specific domains do not share Cookies, referred to as the public suffix list, maintained by Mozilla, which contains a list of all public suffix domains.

Returning to the earlier question, domain=.example.com and domain=example.com are the same, both apply to all subdomains.

If the domain is not set, it defaults to the same domain at least; setting it sends cookies to all subdomains directly.

2. Do You Really Understand How Cookies Work?

Modern browsers are expected to implement Cookies according to RFC6265 standards. For instance, how should Cookies with the same value be handled? Should Cookies be sorted?

Based on my experience, very few engineers understand the quirky history of Cookies and the nuances of the specifications. This seems quite normal. Most engineers know how to set up Cookies, the workings of httpOnly and secure, the logic of path and domain, and this basic knowledge is generally sufficient. After all, to build a product, one cannot be expected to read every specification and fully grasp the ins and outs of Cookies first.

Even if all the above issues are successfully mitigated, you still need to consider how the framework you are using parses and implements Cookies to ensure complete security.

Rethinking: Another Possibility

Based on the various cases mentioned, I started to ponder what the best applications are, shifting my focus to how JavaScript addresses these issues.

In my previous article, I referenced this piece: Cookies are bad for you, so let’s recap.

Requests sent from JavaScript, such as those using ajax or fetch, are defined as CORS requests. CORS requests come with certain conditions and restrictions; for details, refer to my previous article or consult MDN. Here’s how CORS protects your requests:

  1. By default, cookies are not automatically sent/received during cross-origin requests: This prevents CSRF, as CORS requests originate from JavaScript code, thereby rendering typical CSRF attacks ineffective.
  2. CORS requires JavaScript execution: We can place authentication mechanisms in the Authorization request header, which cannot be added by standard form submissions or hyperlink attacks, thus effectively preventing CSRF.
  3. Headers can be added to requests: Fetch allows dynamic header addition for authentication. For example:
fetch('/api', {
  headers: {
    'Authorization': 'Bearer ' + localStorage.getItem('token')
  }
})
  1. If authentication does not rely on Cookies: There’s no need to navigate complex Cookie specifications or worry about CSRF implementation issues.

Now, let’s address the questions everyone is curious about:

  1. If token is stored in localStorage, wouldn’t that be disastrous if XSS occurs?
  2. If the user has JavaScript turned off, won’t that render it non-functional?

Regarding question 1, I’m still contemplating this issue, but it seems no one has addressed, “So if you use Cookies and fall victim to XSS, is that not disastrous too?”

The difference lies in whether the attacker can obtain the token. Let’s consider what happens after an XSS attack:

  • Cookies: XSS occurs → Attacker injects malicious code → Data is captured.
  • localStorage: XSS occurs → Attacker retrieves token → Data is captured.

I’ve noticed that current discussions tend to stop at the second step, yet no one talks about the fact that since the end result is the same, what is the fundamental difference between the two? I find this to be quite unfortunate.

Everyone seems to think Cookies are great and secure, while condemning localStorage, but is that truly the case? Are Cookies truly foolproof? Does the complexity of the specifications lead to more potential issues? This is an area that deserves more discussion.

As for question 2, I believe that if you opt for the localStorage mechanism, then some trade-offs must be accepted. In reality, most users will have JavaScript enabled to browse the web; if JavaScript execution is not allowed, I imagine most SPAs that haven’t implemented SSR would break down entirely.

Conclusion

Although I have some doubts about Cookies and am uncertain if the implementations in frameworks are robust enough, I feel that the evolution of Cookie specifications has become increasingly stable and secure. Frameworks, provided they are open-source, should also be trustworthy. The implementation of SameSite is nearly compatible with most browsers, suggesting a promising future. However, I still hope for deeper discussions and exchanges, which is why I wrote this article.

This reflects my understanding of Cookies, and I welcome any corrections if there are errors.

Additional Notes

After the article was published in the community, there were some suggestions that you can check out in the comments section.

If you found this article helpful, please consider buying me a coffee ☕ It'll make my ordinary day shine ✨

Buy me a coffee