· 8 min read

Rethinking Cookies and CORS

This article was auto-translated from Chinese. Some nuances may be lost in translation.

Introduction

Back in 2019, I published an article on Medium about Cookies and CORS (Dealing with Cookies and CORS). It received quite a bit of positive feedback, and was even plagiarized across the strait. I just hope people remember who the original author is! That article explored what CORS is and how to properly implement CORS across both the frontend and backend—a common issue encountered in web development.

Over a year later, Chrome rolled out a change to its Cookie policy (defaulting SameSite to Lax). This prompted me to rethink the topic, and I shared my thoughts in an article titled Chrome Cookie Policy Changes and Reflections, where I laid out my questions regarding SameSite. Among them were two questions I was still seeking answers to:

  1. Once the server includes SameSite, is it no longer necessary to implement CSRF protection? If a user uses an outdated browser (one that doesn’t support the SameSite attribute) resulting in a security issue, is that vulnerability considered the company’s responsibility or the browser’s responsibility?
  2. Is using Cookies truly a foolproof approach?

Regarding the first question, I figured that as time goes on, more and more browsers will catch up with modern standards. By then, can we completely phase out CSRF mechanisms?

The second question arose from a few cases I came across. Let me elaborate on that here:

在這之前先來一個小測試,請問在 cookie 當中設定 domain=.example.com 跟 domain=example.com 的作用範圍為何?

We’ll get back to the answer shortly. First, let’s look at a classic case: Cookie Bomb.

The article demonstrated a Cookie Bomb using GitHub Pages.

In addition to hosting code, GitHub can be used to host personal websites or blogs via GitHub Pages, where GitHub provides you with a user.github.io domain name. (In the early days, it was directly on username.github.com; you can refer to this article).

In typical browsers, a single cookie (including key, value, expires, etc.) can hold 4KB of data, and a domain can store up to around 50 cookies (varying slightly depending on the browser), amounting to a maximum total of 4KB * 50 = 200KB. The Cookie Bomb technique works by writing a large number of cookies on the client side using document.cookie. Because cookies are included by default in all requests, 200KB is massive for a GET request. As a result, the request is usually blocked directly at the server or Nginx level, causing the server to ignore or reject it.

bomb

Now comes the biggest problem: Cookies are automatically applied across all subdomains (with some conditional exceptions). So when you fall victim on webpage A (after being flooded with cookies), it also takes effect on webpage B and webpage C. As a result, every GitHub Page you visit appears broken.

Strictly speaking, this Cookie Bomb doesn’t cause a direct security vulnerability; it’s more of a prank. Victims simply need to clear the flooded cookies to get requests properly processed by the server again. Still, having cookies shared across this kind of blog-hosting service sounds pretty sketchy.

To prevent this problem, subdomains under certain domains are prevented from sharing cookies with one another via the Public Suffix List, maintained by Mozilla. There, you can find a complete list of all public suffix domains.

Returning to our earlier question: domain=.example.com and domain=example.com behave identically—both apply to all subdomains.

If domain is not set, it is at least restricted to the same domain by default; once set, the cookie is sent to all subdomains.

2. Do You Really Know How Cookies Work?

Modern browsers generally implement cookies according to the RFC 6265 standard. For instance, how should identical cookie names be handled? Do cookies need to be ordered?

In my experience, engineers who truly understand the quirky history of cookies and know all the specification details are extremely rare—and I think that’s completely normal. Most developers know how to set a cookie, how httpOnly and secure work, and the logic behind path and domain, which is usually sufficient. After all, when building products, nobody can afford to read through every specification and master the entire backstory of cookies before writing code.

Even if all of the above are handled properly, you still have to consider how the framework you use parses and implements cookies to achieve true security.

Rethinking: Another Possibility

Based on these various cases, I began to reconsider what best practices look like, which shifted my focus toward JavaScript. Let’s take a look at how these problems can be solved in JavaScript.

As I mentioned in my previous article, I referenced this piece: Cookies are bad for you. Let’s do a quick recap here.

Requests sent by JavaScript, such as AJAX or fetch, are treated as CORS requests (when cross-origin). As long as it is a CORS request, certain conditions and restrictions apply. For details, you can refer to my previous article or MDN. Here, let me outline how CORS protects your requests:

  1. Cookies are not automatically sent/received by default on cross-origin requests: This prevents CSRF, because CORS requests originate from JavaScript code, making standard CSRF attacks ineffective.
  2. CORS requires JavaScript execution: We can place authentication credentials in the Authorization request header. Because standard HTML form submissions and hyperlink attacks cannot attach custom request headers, this effectively prevents CSRF.
  3. Headers can be added to requests: fetch allows you to dynamically attach headers for authentication. For example:
fetch('/api', {
  headers: {
    'Authorization': 'Bearer ' + localStorage.getItem('token')
  }
})
  1. If authentication bypasses the Cookie mechanism: There’s no need to unravel complex Cookie specifications, nor do you have to worry about CSRF implementation issues.

Now, here come the questions everyone wants to ask:

  1. If the token is stored in localStorage, isn’t it game over if you get hit by XSS?
  2. What if the user doesn’t have JavaScript enabled? Wouldn’t the site fail to work?

Regarding question 1, I’m actually still pondering this myself, but nobody seems to have answered: “So if you use cookies and get hit by XSS, is it somehow NOT game over?”

The difference lies in whether the attacker can retrieve the token directly. Let’s think through what happens after an XSS attack occurs:

  • Cookie: XSS occurs → Attacker injects malicious code → Exfiltrates data
  • localStorage: XSS occurs → Attacker retrieves token → Exfiltrates data

Most discussions I see stop at step two, but no one seems to discuss the fact that since the end result is the same, what is the fundamental difference between the two? That’s something I find quite unfortunate.

Everyone assumes Cookies are superior and secure while bashing localStorage on sight. But is that really the case? Are Cookies truly foolproof? Does their specification complexity introduce even more latent issues? Surprisingly, few people seem to discuss this angle.

Regarding question 2, my take is that choosing localStorage naturally comes with trade-offs. In reality, the vast majority of users browse the web with JavaScript enabled. If JavaScript execution is disabled, most SPAs without SSR would simply break anyway.

Epilogue

Although I have some doubts about Cookies and question whether framework-level implementations are sufficiently robust, I do think the Cookie specification has become increasingly stable and secure over time. As for frameworks, as long as they are open source, they should generally be trustworthy. With SameSite implementations now covering almost all major browsers, the future looks fairly promising. Still, I wrote this post in hopes of sparking deeper discussion and exchange of ideas.

The above represents my own understanding of Cookies. If there are any mistakes, please feel free to correct me.

Addendum

After this article was posted to the Facebook group, some valuable feedback was shared—feel free to check out the comments section there.

Related Posts

Explore Other Topics