With users' increasing concern for privacy, various services have gradually started adjusting their privacy and security measures. For example, after the update to Catalina, Mac annoyingly asks for your consent to allow xxx to access certain resources.
Although this enhances security, it can sometimes lead to unfortunate incidents. For instance, the WACOM drawing tablet cannot be used properly.
In 2019, Google announced security policy adjustments at the Google I/O conference, which significantly impact web development. The most significant change is that Chrome will gradually remove support for third-party cookies.
Starting from Chrome 80+, the samesite
attribute in cookies will be set to lax
by default (previously it was set to None in version 80). In the following sections, we will discuss the definition and purpose of samesite
, as well as my thoughts on the matter, considering the reflection on cookies and its implications.
What are Cookies?
Cookies are small storage mechanisms (4KB) implemented on the client-side, controlled by the Response Header sent by the server. In the past, when using cookies, the entire mechanism relied on the browser. The browser determined whether to send cookies and when they expired based on current conditions and header settings. As long as the cookie had not expired and met the conditions, it would automatically be included in every request.
For stateless HTTP requests, cookies allow us to retain some user data, enabling the server to determine the user's current state or perform tracking.
I would say that cookies are both the most convenient and the most dangerous mechanism.
As long as the cookie had not expired and met the conditions, it would automatically be included in every request.
Why do I say this? In general, elements like <form>
, <iframe>
, <a>
, <link>
, <img>
, etc., by default, send cookies. This allows us to achieve the following:
-
Third-party tracking through
<iframe>
- You log in to your Google account, and google.com sends a Set-Cookie response, storing it in the browser.
- You browse a webpage on website B, where a google
iframe
is embedded for tracking purposes. - The iframe sends the cookie to Google.
-
Tracking through
<img>
- You log in to your Google account, and google.com sends a Set-Cookie response, storing it in the browser.
- You browse a webpage on website B, where a page view tracking is sent with
<img src="xxxx.google.com/track/pageview" />
. - The cookie is sent to Google for statistical purposes, revealing the website you are currently browsing.
-
Malicious actions through
<a>
- You log in to website B.
- The website has a poor implementation that deletes a user's URL, which looks like
GET /user/delete
. - A hacker sends you an email with
<a href="xxx.com/user/delete">click me</a>
. - You click on it, and your account gets deleted.
-
Malicious actions through
<form>
- You log in to website B.
- You fill out a form on a malicious website A, thinking it is being submitted to A, but it is actually being submitted to website B (e.g., payment information).
- Your payment data is submitted to website B, resulting in significant losses.
Points 3 and 4 refer to CSRF (Cross-Site Request Forgery) attacks. The defense mechanisms include: 1. Avoid using GET methods for actions with side effects, and 2. Use CSRF tokens for verification, ensuring that requests originate from trusted sources.
CSRF effectively solves the security issues caused by the cookie mechanism. Experienced engineers know how cumbersome and error-prone implementing a stateful CSRF token mechanism can be, especially in high-traffic scenarios. The mention of CSRF tokens often elicits a dismayed reaction.
In fact, as early as 13 years ago, someone proposed not allowing tags like <img>
and <link>
to send cookies (article). However, the ultimate 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 address the problems caused by CSRF, Chrome 51+ introduced the SameSite attribute to prevent CSRF attacks. This attribute allows the author to decide whether to send cookies and offers three options:
strict
- Cookies are not sent under any circumstances. Although the most secure option, it may not always be desirable. For example, if you link from website B to YouTube, not sending cookies would result in an unsigned-in state.lax
- Cookies are only sent when navigating to the target URL using a GET request.none
- Cookies are sent by default.
Chrome 80+ Cookie SameSite Policy
The recent buzz around SameSite cookies refers to the change in Chrome 80+ where SameSite=none
is now set to SameSite=lax
by default, ensuring user security.
Well, that was a brief background introduction. Now let's discuss my reflections.
Reflection 1: Does adding SameSite=lax
eliminate the need for CSRF tokens?
Since the SameSite standard is relatively new (though not that new), what happens if a user is using an older browser that does not support SameSite? Could they be vulnerable to CSRF attacks?
In that case, this policy change seems somewhat redundant for me. Apart from preventing third-party tracking, implementing CSRF tokens is still necessary to effectively protect against attacks for our own services.
In reality, the implementation of cookies varies among different browsers, and cookies themselves pose several security issues, such as:
- Safari 12 allowing JavaScript to overwrite httpOnly cookies
- Complexity of specifications (e.g., the difference between
domain=.example.com
anddomain=example.com
) - Sharing of cookies between domains
- (To be continued...)
In summary, I believe that relying on browser mechanisms for cookie implementation is not always convenient. This made me think of an alternative approach, which leads to my second reflection.
Reflection 2: What if we try to minimize the use of cookies?
I found an article online titled Cookies Are Bad for You, which presents some practices worth considering.
The key is to choose a mechanism that is controlled by the web application, not the browser.
If we have to rely on browser mechanisms for cookies, why not avoid using cookies altogether and transfer the entire implementation to JavaScript? What does this mean?
- In JavaScript, we can use the
credentials: include
option in thefetch
API to decide whether to send cookies, in conjunction with CORS headers. - JavaScript can effectively prevent CSRF attacks (explained below).
- It does not rely on the implementations of various browsers.
Regarding the prevention of CSRF attacks, we can require any request that requires user identification to include a request header, such as an Authorization header, to avoid CSRF attacks without the need for CSRF token mechanisms.
When submitting a form, we can also avoid using browser mechanisms directly and use JavaScript APIs instead.
const form = new Form()
form.append('keyA', 'valueA');
fetch('/my-api', { body: form, method: 'POST', headers: { 'Authorization': 'xxx' } })
However, using JavaScript entails considering expiration mechanisms, sending requests manually, and other factors:
- Where to store the data?
- What if there is an XSS vulnerability?
- What if the user disables JavaScript?
Where to store the data?
Data (access tokens) can be stored in memory, localStorage
, sessionStorage
, or even indexDB
. I understand that this may sound somewhat peculiar, but if we encounter an XSS vulnerability, let's keep reading.
Firstly, avoid storing too much sensitive information on the client-side. Set shorter expiration times for access tokens to limit the impact if they are leaked. Additionally, employ a refresh token mechanism to ensure a good user experience.
What if there is an XSS vulnerability?
I would say that every mechanism carries some risk. Even cookies have had security vulnerabilities in the past. With the help of frontend frameworks, we can potentially mitigate most XSS vulnerabilities.
What if the user disables JavaScript?
Disabling JavaScript is a trade-off. Look at Facebook, YouTube, and Netflix. Don't they all require JavaScript to be enabled?
Regarding screen readers, I believe that in the future, more screen readers may include basic JavaScript to enhance the user experience. Although recent trends have led to JavaScript bundles being several hundred KB in size, whether it's related to screen readers or accessibility, JavaScript can provide more refined interactions.
So, does this mean that we should use JavaScript for actions like <img>
and <form>
and make API calls? In the era of SPAs, more and more services are using XHR for submitting APIs. Apart from the inconvenience of relying on JavaScript and additional processing, this approach brings more reliable security.
Reflection 3: OAuth
This is mentioned in the article.
Through the OAuth protocol, we can exchange tokens with an authorization server, store the tokens on the client-side for use, and then use HMAC algorithms to ensure that the request method and URL are correct.
Conclusion
I personally believe that security and convenience are two sides of the same coin. Although shifting the entire implementation, verification, and expiration mechanisms to the server-side may seem troublesome, I used to think that cookies were secure and convenient. However, with this policy change and the various cases I have come across, it made me rethink the topic. There may be perspectives I have overlooked in this article, so I welcome any feedback.