SameSite cookies provide a mechanism to recognize what led to the loading of a page. Whether it was through clicking a link on another website, submitting a form, loading inside an iframe, using JavaScript, etc.
Identifying how a page was loaded is crucial for security. The serious vulnerability known as Cross-Site Request Forgery (CSRF) has been with us for over twenty years, and SameSite cookies offer a systematic way to address it.
A CSRF attack involves an attacker luring a victim to a webpage that inconspicuously makes a request to a web application where the victim is logged in, and the application believes the request was made voluntarily by the victim. Thus, under the identity of the victim, some action is performed without the victim knowing. This could involve changing or deleting data, sending a message, etc. To prevent such attacks, applications need to distinguish whether the request came from a legitimate source, e.g., by submitting a form on the application itself, or from elsewhere. SameSite cookies can do this.
How does it work? Let’s say I have a website running on a domain, and
I create three different cookies with attributes SameSite=Lax
,
SameSite=Strict
, and SameSite=None
. Name and value do
not matter. The browser will store them.
- When I open any URL on my website by typing directly into the address bar or clicking on a bookmark, the browser sends all three cookies.
- When I access any URL on my website from a page from the same website, the browser sends all three cookies.
- When I access any URL on my website from a page from a different
website, the browser sends only the cookies with
None
and in certain casesLax
, see table:
Code on another website | Sent cookies | |
---|---|---|
Link | <a href="…"> |
None + Lax |
Form GET | <form method="GET" action="…"> |
None + Lax |
Form POST | <form method="POST" action="…"> |
None |
iframe | <iframe src="…"> |
None |
AJAX | $.get('…'), fetch('…') |
None |
Image | <img src="…"> |
None |
Prefetch | <link rel="prefetch" href="…"> |
None |
… | None |
SameSite cookies can distinguish only a few cases, but these are crucial for protecting against CSRF.
If, for example, there is a form or a link for deleting an item on my
website's admin page and it was sent/clicked, the absence of a cookie created
with the Strict
attribute means it did not happen on my website but
rather the request came from elsewhere, indicating a CSRF attack.
Create a cookie to detect a CSRF attack as a so-called session cookie without
the Expires
attribute, its validity is essentially infinite.
Domain vs Site
“On my website” is not the same as “on my domain,” it's not about
the domain, but about the website (hence the name SameSite). Although the site
often corresponds to the domain, for services like github.io
, it
corresponds to the subdomain. A request from doc.nette.org
to
files.nette.org
is same-site, while a request from
nette.github.io
to tracy.github.io
is already
cross-site. Here it is nicely
explained.
<iframe>
From the previous lines, it is clear that if a page from my website is loaded
inside an <iframe>
on another website, the browser does not
send Strict
or Lax
cookies. But there's another
important thing: if such a loaded page creates Strict
or
Lax
cookies, the browser ignores them.
This creates a possibility to defend against fraudulent acquisition of
cookies or Cookie
Stuffing, where until now, systemic defense was also lacking. The trick is
that the fraudster collects a commission for affiliate marketing, although the
user was not brought to the merchant's website by a user-clicked link. Instead,
an invisible <iframe>
with the same link is inserted into the
page, marking all visitors.
Cookies without the SameSite Attribute
Cookies without the SameSite attribute were always sent during both same-site
and cross-site requests. Just like SameSite=None
. However, in the
near future, browsers will start treating the SameSite=Lax
flag as
the default, so cookies without an attribute will be considered
Lax
. This is quite an unusually large BC break in browser behavior.
If you want the cookie to continue to behave the same and be transmitted during
any cross-site request, you need to set it to SameSite=None
.
(Unless you develop embedded widgets, etc., you probably won't want this often.)
Unfortunately, for last year's browsers, the None
value is
unexpected. Safari 12 interprets it as Strict
, thus creating a
tricky problem on older iOS and macOS.
And note: None
works only when set with the Secure
attribute.
What to Do in Case of an Attack?
Run away! The basic rule of self-defense, both in real life and on the web. A huge mistake made by many frameworks is that upon detecting a CSRF attack, they display the form again and write something like “The CSRF token is invalid. Please try to submit the form again”. By resubmitting the form, the attack is completed. Such protection lacks sense when you actually invite the user to bypass it.
Until recently, Chrome did that during a cross-site request—it displayed
the page again after a refresh, but this time sent the cookies with the
Strict
attribute. So, the refresh eliminated the CSRF protection
based on SameSite cookies. Fortunately, it no longer does this today, but
it's possible that other or older browsers still do. A user can also
“refresh” the page by clicking on the address bar + enter, which is
considered a direct URL entry (point 1), and all cookies are sent.
Thus, the best response to detecting CSRF is to redirect with a 302 HTTP code elsewhere, perhaps to the homepage. This rids you of dangerous POST data, and the problematic URL isn't saved to history.
Incompatibilities
SameSite hasn't worked nearly as well as it should have for a long time, mainly due to browser bugs and deficiencies in the specification, which, for example, didn't address redirections or refreshes. SameSite cookies weren't transferred during saving or printing a page, but were transferred after a refresh when they shouldn't have been, etc. Fortunately, the situation is better today. I believe that the only serious shortcomings in current browser versions persist, as mentioned above for Safari.
Addendum: Besides SameSite, the origin of a request can very recently be distinguished also by the Origin header, which is more privacy-respecting and more accurate than the Referer header.