· 9 min read

Chrome Cookie Policy Adjustments and Reflections

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

As users place greater emphasis on privacy, various services have gradually begun adjusting their privacy and security settings. For instance, after Mac updated to Catalina, it annoyingly asks whether you agree to allow xxx to access certain resources.

Although security has indeed become more stringent, it has also occasionally caused some disasters—such as WACOM drawing tablets failing to work properly.

At Google I/O 2019, Google also announced security policy adjustments. Among them, the one with the biggest impact on current web development is likely Chrome’s gradual phasing out of support for third-party cookies.

Specifically, starting from Chrome 80+, the samesite attribute in Cookies will be set to lax by default (it was None prior to version 80). Below, we will start from the definition of samesite, what it is used for, and reflections on Cookies to talk about my thoughts on the whole matter.

A Cookie is a small client-side storage mechanism (4KB) that can be controlled by Response Headers sent back from the Server. Historically, once Cookies were used, the implementation of the entire mechanism usually relied on the browser side. The browser would decide whether Cookies could be sent, when they expire, and so on, based on current conditions and configured headers. As long as a Cookie has not expired and matches the criteria, the Cookie is automatically included and sent with every request.

For stateless HTTP Requests, the Cookie mechanism allows us to retain some user data so the Server can determine the current user state or perform tracking.

I would say this is both the most convenient and the most fatal mechanism of cookies:

As long as a Cookie has not expired and matches the criteria, the Cookie is automatically included and sent with every request

Why do I say this? Under normal circumstances, tags like <form>, <iframe>, <a>, <link>, <img>, etc., will send Cookies by default. Therefore, we can do things like the following:

  1. Third-party tracking via iframe

    • You log into your Google account; google.com returns Set-Cookie and it is stored in the browser.
    • You browse website B, which embeds a Google iframe for tracking.
    • The iframe sends the cookie to Google.
  2. Tracking via <img>

    • You log into your Google account; google.com returns Set-Cookie and it is stored in the browser.
    • You browse website B; website B sends an image request via <img src="xxxx.google.com/track/pageview" /> when the page loads.
    • The cookie is sent to Google for analytics, letting them know which website you are currently browsing.
  3. Doing malicious things via <a>

    • You log into website B.
    • This website has a terrible implementation where the delete-user URL looks like GET /user/delete.
    • A hacker sends you an email containing <a href="xxx.com/user/delete">click me</a>.
    • You click it, and your account gets deleted.
  4. Doing malicious things via <form>

    • You log into website B.
    • On malicious website A, you fill out a form, but it actually submits to website B—for example, payment information.
    • Your payment information is submitted to website B, causing significant financial loss.

Points 3 and 4 are the familiar CSRF (Cross-Site Request Forgery). The defense strategies are: 1. Do not use the GET method for operations with side effects; 2. Use CSRF tokens for verification to ensure the request truly comes from your trusted source.

Therefore, to defend against CSRF attacks, the most common approach is to embed a CSRF Token in the HTML. Whenever the server executes an operation, it first checks whether the request contains a CSRF Token to ensure the source is genuinely its own service.

CSRF indeed addresses the security vulnerabilities caused by the Cookie mechanism. For experienced engineers, everyone knows how tedious and error-prone it is to implement a stateful CSRF token mechanism (especially under high traffic). Whenever CSRF Tokens are mentioned, everyone cringes.

In fact, more than 13 years ago, someone proposed not allowing tags like <img> and <link> to send Cookies (article), but the response received was:

The attack described here is well-known and called “Cross-site request forgery”. Most believe that it is the web application’s responsibility to fix it, not the web browser’s.

To solve the issues brought by CSRF, Chrome 51+ introduced the SameSite attribute to prevent CSRF attacks. Its principle is to give the choice of whether to send cookies back to the implementer, offering three values:

  • strict: Cookies are never sent under any circumstances. While safest, this may not always be desirable—for example, if I follow a link on website B to YouTube, I will appear logged out because the Cookie isn’t sent.
  • lax: Cookies are only sent when a GET request navigates to the target URL.
  • none: Cookies are sent by default.

The recent buzz around samesite cookies refers to Chrome 80+ changing the default from SameSite=none to SameSite=lax to ensure user security.

Alright, that covers the background. Now let’s dive into my reflections.

Reflection 1: So does adding SameSite=lax mean we no longer need to implement CSRF Tokens?

Since the SameSite standard is relatively new (well, not that new anymore), could users with older browsers that do not yet support samesite still be vulnerable to CSRF attacks?

If so, this policy change feels a bit lackluster to me. Aside from preventing third-party tracking, your own services must still implement CSRF Tokens to effectively defend against attacks.

In fact, Cookie implementations vary across different browsers, and Cookies have caused quite a few security issues, such as:

In short, the point I want to make is that relying on browser-based Cookie mechanisms can sometimes be inconvenient, which led me to another approach: Reflection 2.

Reflection 2: What if we try to avoid Cookies altogether?

Doing a quick online search, I found this article (Cookies Are Bad for You). The approach mentioned in it is quite worth referencing:

The key is to choose a mechanism that is controlled by the web application, not the browser

Since using Cookies forces us to rely on browser mechanisms, why not avoid cookies entirely and hand over the full implementation to JavaScript? What does this mean?

  • In JavaScript, you can use credentials: include in fetch to decide whether to send Cookies, and pair it with CORS Headers.
  • CSRF attacks can be effectively prevented via JavaScript (detailed below).
  • No need to rely on the implementation quirks of different browsers.

Regarding ways to prevent CSRF attacks, we can require every request needing user identity to include a Request Header, such as an Authorization header. This prevents CSRF attacks without needing to implement any CSRF Token mechanism.

When submitting forms, instead of directly using browser mechanisms, we use JavaScript APIs:

const form = new Form()
form.append('keyA', 'valueA');

fetch('/my-api', { body: form, method: 'POST', headers: { 'Authorization': 'xxx' } })

However, besides having to handle expiration mechanisms and sending requests manually, using JavaScript also requires considering the following points:

  • Where is data stored?
  • What happens in case of XSS?
  • What if the user disables JavaScript?

Where is data stored?

Data (access tokens) can be stored in memory, localStorage, sessionStorage, or even indexedDB. I know this might sound a bit strange at first—what if we encounter XSS? Let’s read on.

First of all, avoid storing excessive sensitive information on the client side, and keep the access token lifespan as short as possible to prevent a widespread impact if a token leaks. Then, use a refresh token mechanism to preserve user experience.

What about XSS?

I would say every mechanism carries a certain degree of risk. Even Cookies have had their share of exposed security vulnerabilities. With the help of frontend frameworks, we might already be avoiding the majority of XSS vulnerabilities.

What if the user disables JavaScript?

Disabling JavaScript is a tradeoff, in my opinion. Look at Facebook, YouTube, Netflix—don’t all these services require you to enable JavaScript?

As for Screen Readers, I think more screen readers will likely incorporate basic JavaScript in the future to deliver better experiences. Although recent trends tend to make JavaScript bundles hundreds of KB in size, using JavaScript can offer much more refined interactions for both screen readers and accessibility.

So does this mean even img, form, and the like must use JavaScript to call APIs? I’d say in an era dominated by SPAs, more and more services already use XHR for API submissions. Beyond requiring JavaScript and having to write a bit more code, what you get in return is much more reliable security.

Reflection 3: OAuth

This was mentioned in the article.

Through the OAuth protocol, we can exchange tokens with an authorization server, store the token on the client side for use, and then verify the request method and request URL via HMAC algorithms.

Conclusion

I believe that security and convenience are inherently two sides of the same coin. Moving all implementation, validation, and expiration mechanisms to the server side can be tedious. In the past, I also felt that Cookies were secure and very handy. Seeing this policy change now, combined with various incidents I’ve observed before, made me rethink everything. There might be viewpoints not considered in this article, and feedback is warmly welcome.

Related Posts

Explore Other Topics