The first article gave a brief explanation about what SameSite Cookies actually mean, but it was very brief indeed and quite a bit simplified. The other article focused on solving the Chrome vs. Safari implementations issue, and I wanted to keep the introduction short. However I feel this topic is very important and deserves an explanation that really answers all questions. So, without further ado, I want to take you on a journey into the deep rabbit hole of SameSite Cookies.
Back in the days of the good old web 1.0 cookies were implemented to preserve and persist state information on the side of the User Agent (mostly a web browser). The nitty gritty details are all written down in RFC 6265, but it all boils down to cookies being key-value pairs that can be written to the User Agent using the
Set-Cookie http header in the response. Besides the cookie name (the key) and its value, other options can additionally be set to control when a cookie should expire (
expires or the newer
max-age), if it may be sent via http or only via https (
If a cookie is set and not yet expired, the User Agent sends the cookie along to the server with every request that matches the
path values that can also be set on a cookie. What do they do?
Let’s assume there is a website on
www.example.com and it allows you to log in to customize your experience. Your log in session is stored in a session cookie. The website also offers a store at
shop.example.com which uses the same login mechanism. With the default behavior, the cookie would be set to
www.example.com and not be sent to
shop.example.com, so you would have to log in at both subdomains explicitly. When the website sets the cookie using the
domain=example.com parameter, the cookie will be sent to all subdomains of example.com, and so the user only needs to login once. You can only use the
domain option to narrow or broaden the scope of the cookie on your own domain: The value set of the
domain option must be part of the host name that is sending the
Set-Cookie header. So you can’t set a cookie for
thinktecture.com as that would introduce a security issue.
path allows you to further narrow down the places where a cookie will be sent to. If, for example, the website sets the cookie and states
domain=example.com; path=/blog, then the cookie will only be sent to the server if the request starts with
/blog, so it would be sent to
shop.example.com/blogroll, but not to
Without cookies the web would not work as it does today. However, since the mid 90ies when cookies were invented, the web changed quite a lot and so did the attack methods on web applications. Let’s think through a possible attack that could happen:
Imagine that after login our web server at
www.example.com sets a session cookie like this:
Set-Cookie: session=89bea4bb-85d1-4f8b-b4fa-a9b8db015e2b; Max-Age=2600000; Domain=example.com; Path=/; Secure; HttpOnly. Whenever the browser sends a request using https, our server can nosh the fine cookie and from its taste it knows that there’s a session with the GUID
89bea4bb-85d1-4f8b-b4fa-a9b8db015e2b.It can then answer the request and amend it with information from the user that is currently logged in with this session id. The web shop at
shop.example.com is a single-page web application and it communicates with
api.example.com, which also uses the same session cookie information.
An attacker now sets up a new websites. Let’s call it
example-com-product-reviews.attacker.com. The attacker lures users onto his website by providing additional information for products only found on
example-com-product-reviews.attacker.com is not matched by the
example.com domain option, and secondly the cookie is set to
Fetch API (the modern replacement of the old
XMLHttpRequest object) to send a request to the shops API. It could, for example send a malicious request to
While the user reads the information presented on
api.example.com, which matches the domain option from our session cookie, and the path also matches, the browser indeed attaches the cookie to the request. The API server receives the request, finds the attached session cookie, determines the user and adds a very expensive item to the shopping cart – without the user even noticing. Another forged request like
POST https://api.example.com/checkoutUsingAlreadyProvidedPaymentMethod could then cause real damage.
This attack method is known as Cross Site Request Forgery, or short CSRF. If you want you can read more about that in the CRSF Page on OWASP.
Now, there is another technology in place that could help prevent these kinds of attacks. It is called CORS (Cross Origin Resource Sharing) and involves a bit of knowledge on how to apply it correctly. For now, we assume that somehow the common development setting
Allow any origin made it on the production server on
api.example.com instead of a specific setting to only allow requests from
shop.example.com. And while this might sound a bit constructed, configuration mistakes can happen – and sadly they also do happen all the time. It just takes a project setup at a time where the final domains aren’t known yet, so
AnyOrigin is used for development purposes. Later when the shop needs to be rolled out everybody is so stressed out that nobody thinks about checking and reviewing all these
// Todo: Configure this to the actual shop domain, OR ELSE! comments left in the codebase – in good faith that this will be done before release.
But its not only POST requests: Since you are logged in,
example-com-product-reviews.attacker.com can also add an image tag to its website which looks like this:
<img src="https://shop.example.com/profilepicture.jpg" />. The browser will send a GET request to the shop requesting the current users profile image, and it will send the cookie with that request. The server is able to respond with the correct image. This way the attacker can display the correct profile image of the current user on their website, which may make them appear even more legitimate.
But not everything is bad: where we need cross-site cookies
So, our main issue here is that a browser will happily send along all cookies of a domain with every request to that domain. In the previous scenario this is an attack, but there might be occasions where someone actually wants and requires this scenario.
Imagine you build a website and embed a YouTube video. The video is requested from Googles YouTube servers. If you are logged in to YouTube, your login cookie gets sent along just as with the profile picture in the example above. YouTube now is able to offer the user a ‘Watch later’ button or the possibility to add the video to the users personal playlist.
This is a desired feature, so we have to think about how to block malicious attacks while still being open for legitimate usage.
All cookies are equal, but some are more equal than others
Before we delve into the actual SameSite Cookie option that can help us with that, I want to take a minute to differentiate these cookies a bit more.
First of all, we have first-party cookies. These are the cookies that belong to the current domain as by their settings. If you are on
example-com-product-reviews.attacker.com, all cookies with the domain set to exactly this or to
attacker.com are considered first party cookies. Then there are third-party cookies. This are all cookies for all other sites, like
As you see, a cookie can be both a first-party cookie and a third-party cookie, just not at the same time, depending on the domain the user currently is at.
But what is the ‘same’ domain as per the SameSite rules? Usually, the combination of second (example.com) and top level domain (example.com) is the common denominator. For example when the web application served from
www.example.com sends a request to
shop.example.com, this would be considered as a same-site request.
However, there are exceptions from that. One prominent example would be
co.jp, where commercial domain registrants get a third level domain all under the
co second level domain. Requests between
attacker.co.uk should not be same-site, although they are both under
co.uk. Another example are subdomains that can be used by different parties, for example
attacker.azurewebsites.net for Microsoft Azure or
*.compute.amazonaws.com for Amazons cloud services. Here you also want stronger isolation, as the sibling subdomains are absolutely not under your control.
Since the browser needs to decide whether a request is same-site or not, and technically anybody can register a domain and offer its subdomains for such purposes, there needs to be a way for browser vendors to figure out if a domain should be treated as same-site or not. This is where the Public Suffix List comes in handy. This list provides the domains that are a “public suffix” (aka under which different parties can register their own subdomains / prefixes).
Based on the information provided by this list, the Browsers are able to distinguish between first and third party cookies based on the current domain.
Controlling SameSite Cookie behavior
Now that we know that we have two types of cookies, we can start controlling what a browser does with them respectively.
The original SameSite policy was suggested in the Same-site Cookies draft. This draft specifies the new
SameSite option that is possible when setting a cookie and allows two values:
Lax. This was designed as backwards-compatible by maintaining the original behavior when no
SameSite option is set at all. Sadly, this feature was not really broadly adopted.
Then M. West from Google published a new draft to the web standards track named Incrementally Better Cookies that introduced the new setting
None, requires the
Secure flag for
SameSite=None cookies and – this is the real game changer – changing the default behavior of cookies set with no SameSite option to
Lax (and thus breaking backwards-compatibility). The whole changes that are supposed to replace the old RFC 6265 are combined in the new Cookies: HTTP State Management Mechanism document.
Now, what do these SameSite Cookie options do?
Let’s go with the easiest settings first,
None: This is the same as the original behavior of cookies where the SameSite option did not exist: Every cookie will be sent along with every request, as long as the values of domain and path match.
The second is
Strict: This defines that cookies are only sent to the server when the request is initiated by a first-party context. What that actually means is that when you enter
https://shop.example.com into your Browsers address bar, or you click on a link to that URL, this very first request to the server is not yet a first-party context (as it was initiated by an unknown party). Only subsequent requests that originate from that loaded page are considered being in the first-party context and as such provide the cookie to the server.
Now, for our session cookie this is a bit too harsh: The first request to
shop.example.com would not provide the session cookie, so the initial response of the shop would be the same as for a not-logged in user, but the next navigation on the application would show the user as logged in.
This is where
Lax provides its services: This specifies that the cookie will be sent to the server also on top-level navigations to our site, i.e. when the user follows a link to our shop. So even on the first navigation to our site the cookie will be sent and the user immediately appears as logged in.
The most important thing is that
Lax both will prevent sending of the cookies for all other requests from a 3rd party context. This means that in our example the web site served from
example-com-product-reviews.attacker.com both described attacks won’t work anymore. The image-tag requesting the user profile picture from our shop will not send the cookie along to
shop.example.com (so the fake page won’t be able to show the real users image). Nor will the browser send the cookie along with the POST requests to
The Chrome vs. Safari issue
Most browser vendors already implemented the older Same-site Cookies suggestions. Chrome now rushes ahead implements the Incrementally Better Cookies specification starting with version 80 and changing the default to
The problem with that is that Safari recognizes the SameSite option starting with version 12, but their implementation has a bug: It interprets invalid values as if
SameSite=Strict had been specified, and for it only
Lax are valid values, as the older specification did not yet specify
This bug is fixed in Safari 13, but sadly the Safari team stated that this won’t be backported and older iOS and macOS versions won’t be updated anymore with that fix. The actual problem is that a web server can’t satisfy the bugged implementations in Safari and the new Chrome implementation with a single approach. It is, sadly, required to detect if the browser is one of the affected Safari versions and omit the SameSite option when sending cookies to these browsers.
This is how the most common browsers interpret the SameSite setting. (Browser versions older than the lowest specified version do not yet implement SameSite at all and consider everything as not specified):
C# / .NET
Both Firefox and Edge have stated an ‘Intent to implement’ for the Incrementally Better Cookies draft. They, too, will change their default behavior to
SameSite=Lax and additionally require
Secure to be specified for Cookies with the
SameSite=None option, but as of now there is no information about when that will happen (Source: for Firefox, and for Edge).
We explored how cookies worked since the dawn of the world wide web, and how that could be abused by CSRF attacks. We then learned about first- and third party cookies and the Public Suffix List.
Then we learned what changes were proposed with the Same-Site options for cookies, and how the Chrome team at Google now pushes forward to get these security improvements out into the field.
Lastly we looked ad the different implementations of the SameSite option and how that might affect the way we have to set cookies for certain browser versions.
Hopefully, you learned a bit more about Cookies, the SameSite attribute and how to handle them with care.