In my last articles on how to prepare your IdentityServer for Chromes SameSite Cookie changes and how to correctly delete your SameSite Cookies in Chrome 80 I explained the changes that Chrome did to its SameSite Cookie implementation, how that might affect you and how to avoid problems arising from these changes.

In this article:

SameSite Cookies In A Nutshell
Sebastian Gingter is architect consultant and loves to explain things at Thinktecture. He focuses on backends with .NET Core and IdentityServer.

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 (Secure), and if JavaScript in the browser should be able to read and write the cookie value or if the cookie should be hidden from JavaScript and only be sent to the server (HttpOnly).

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 domain and path values that can also be set on a cookie. What do they do?

Let’s assume there is a website on 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 which uses the same login mechanism. With the default behavior, the cookie would be set to and not be sent to, so you would have to log in at both subdomains explicitly. When the website sets the cookie using the parameter, the cookie will be sent to all subdomains of, 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 as that would introduce a security issue.

The 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; path=/blog, then the cookie will only be sent to the server if the request starts with /blog, so it would be sent to and, but not to

Security crumbles

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 sets a session cookie like this: Set-Cookie: session=89bea4bb-85d1-4f8b-b4fa-a9b8db015e2b; Max-Age=2600000;; 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 is a single-page web application and it communicates with, which also uses the same session cookie information.

An attacker now sets up a new websites. Let’s call it The attacker lures users onto his website by providing additional information for products only found on When a user visits his site, he returns a website with some content and additional JavaScript. The JavaScript can’t read our session cookie. First of all, is not matched by the domain option, and secondly the cookie is set to HttpOnly, so JavaScript can’t touch it anyways. This way the attacking JavaScript can’t do anything with our cookie directly. But what the JavaScript can do is use the 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 POST

While the user reads the information presented on, the JavaScript executes and the browser sends the request. Since this request is sent to, 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 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 instead of a specific setting to only allow requests from 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, can also add an image tag to its website which looks like this: <img src="" />. 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, all cookies with the domain set to exactly this or to are considered first party cookies. Then there are third-party cookies. This are all cookies for all other sites, like or

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 ( and top level domain ( is the common denominator. For example when the web application served from sends a request to, this would be considered as a same-site request.

However, there are exceptions from that. One prominent example would be or, where commercial domain registrants get a third level domain all under the co second level domain. Requests between and should not be same-site, although they are both under Another example are subdomains that can be used by different parties, for example and for Microsoft Azure or * 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: Strict and 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 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 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 Strict and 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 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 (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 Lax.

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 Strict and Lax are valid values, as the older specification did not yet specify None.

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. provides a nice overview about the different browsers and versions and whether SameSite cookies are supported or not on their ‘SameSite’ cookie attribute page.

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):

Apache 2
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.


Current articles, screencasts and interviews by our experts

Don’t miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.

Related Articles
Configuring Lazy Loaded Angular Modules
Making our Angular modules configurable is an important step in building a reusable architecture. Having used Angular for a while you might be familiar with the commonly used forRoot() and forChild() functions, that some modules provide you with. But what is the best way to provide configuration in these cases?
Master Web Component Forms Integration – with Lit and Angular
When a company has cross-framework teams, it is a good choice to use Web Components to build a unified and framework-independent component library. However, some pitfalls are to consider when integrating these components into web forms. Therefore, for a better understanding, we will look at two possible approaches and try to integrate them into an Angular form as an example.

Notice: All code samples are available on Github!
Dependency Injection Scopes in Blazor
The dependency injection system is a big part of how modern ASP.NET Core works internally: It provides a flexible solution for developers to structure their projects, decouple their dependencies, and control the lifetimes of the components within an application. In Blazor - a new part of ASP.NET Core - however, the DI system feels a bit odd, and things seem to work a bit differently than expected. This article will explain why this is not only a feeling but indeed the case in the first place and how to handle the differences in order to not run into problems later on.