Few weeks ago Google Chrome has started to gradually enforce a new behavior for the SameSite attribute of cookies on its latest stable version of the browser, Chrome version 80. This is also when the checkout process in the Sitecore Commerce 9.1 solution of one of my clients started to fail.

In this blog post I will describe the steps taken to troubleshoot and reproduce this issue, the requirements to manage the SameSite attribute in a .NET solution and its compatibility with Sitecore, and finally the implemented solution to adjust the SameSite attribute value of the authentication cookie.

The SameSite attribute

The SameSite attribute allows developers to decide if a cookie should be restricted to a first-party (same-site) context or if it should be available on third-party (cross-domain) contexts. This attribute can have three values:

  • Strict – The cookie is restricted to a first-party context only.
  • Lax – The cookie is included in cross-domain “top-level” GET requests, but not in POST requests.
  • None – The cookie is not restricted to a third-party context.

As explained on the Google Developers blog, the following two changes has been introduced with the release of Chrome 80:

  • Cookies without a SameSite attribute will be treated as SameSite=Lax.
  • Cookies with SameSite=None must also specify Secure, meaning they require a secure context.

This means that if your solution requires to share a cookie in a third-party context, and this cookie doesn’t have the SameSite attribute value set to None and it is not marked as Secure, the cookie will be dropped by Chrome in cross-domain requests.

This is what broke the checkout process in the Sitecore Commerce solution of one of my clients.

A Sitecore Commerce solution with a federated payment provider

In this Sitecore Commerce solution, the checkout process is integrated with a federated payment provider that requires authenticated storefront users to be redirected to an external secured payment gateway platform to perform a payment for their order. If the payment transaction is successfully executed, the user is redirected back to the Sitecore storefront website via a POST request, used to share payment transaction details back to Sitecore Commerce.

This POST request is executed from the federated payment provider third-party context to the storefront website, so it is a cross-domain request. This request needs to include the authentication cookie associated to the storefront website, otherwise it will be seen by the storefront website as a request performed by an anonymous user, causing the checkout process to fail.

When Google Chrome started to gradually enforce the changes introduced in Chrome version 80, the checkout process started to gradually fail for Chrome users as well.

Investigating the issue

These changes have not been enforced to all Chrome browser users at the same time, but they have been gradually enforced starting from mid of February. So not all 100% Chrome users are affected by this new behavior yet. The gradual enforcement tricked me while I was trying to identify the root cause of the issue and I was looking for consistency in the failure behavior!

The Sitecore Commerce logs analysis helped me to detect that the issue started to occur on March 9th. On the same date Google Chrome started to enforce the new SameSite attribute behavior on a larger population, according to the Chromium Project release status updates.

The IIS logs analysis and, specifically, the distribution of anonymous POST requests (the ones affected by the dropped authentication cookie and initiated from the federated payment provider website) by user-agent helped me to identify that only Chrome version 80 users were affected by this issue.

Reproducing the issue

One of the first steps to perform when troubleshooting an issue is to try to reproduce it consistently. Since this issue affected the checkout process of a commerce solution, it was mandatory to identify the steps to reproduce it on a non-production environment where fake payments could be performed using a sandbox instance of the federated payment provider.

When I tried to reproduce the issue on my Chrome version 80 browser, I couldn’t reproduce the issue. The reason was because of the gradual enforcement of the new behavior. My browser was not included yet in the list of enforced users.

The new SameSite attribute behavior can be enforced in Chrome following the three steps described on the Testing Tips section on the Chromium Project website, as follows:

  1. Go to chrome://flags and enable both #same-site-by-default-cookies and #cookies-without-same-site-must-be-secure. You must set them to “Enabled” rather than “Default”.
  2. Restart Chrome for the changes to take effect.
  3. Verify that your browser is applying the correct SameSite behavior by visiting this test site and checking that all rows are green.

Requirements to manage the SameSite attribute in ASP.NET applications

Once I was able to reproduce the issue consistently in my Chrome version 80 browser, I started to implement a fix and I discovered that the SameSite attribute management has two important requirements in ASP.NET applications.

The version of the ASP.NET Framework

As described on the Microsoft documentation page about working with the SameSite attribute in ASP.NET, Microsoft does not support .NET versions lower that 4.7.2 for writing the same-site cookie attribute.

According to this Sitecore KB article, the Sitecore 9.1 platform is compatible with ASP.NET Framework 4.7.1, so it is very likely that your custom projects have been implemented targeting the same version of the ASP.NET Framework, like it was done for my Sitecore solution. Being compatible with ASP.NET Framwork 4.7.1 it means that the Sitecore solution has been tested with this specific version of the framework, but it doesn’t mean that it will not work with an higher version. It hasn’t just been tested officially, as confirmed by the Sitecore Support team.

In order to work with the SameSite attribute in my solution I had to upgrade it to target the ASP.NET Framework 4.7.2. This means that I had to upgrade all the projects in my Visual Studio solution, modify the target framework version of any installed NuGet packages and the target framework version defined in the web.config of my website. A lot of changes, but no compatibility issues have been detected so far.

If any compatibility issue occurs, Sitecore Support recommends to upgrade to Sitecore 9.2+ and .NET Framework 4.8.

The .NET Framework Windows Updates

The SameSite attribute support was originally implemented in .NET 4.7.2, but the new behavior recently enforced by Chrome has started to be supported with the updates release in November 2019.

Windows updates should always be installed regularly on any machine. If a machine or a development environment has been left behind with the installation of Windows updates, this documentation page helps to identify the correct Windows update for each specific version of Windows that supports the new SameSite attribute behavior.

The solution

If you can control the creation of a cookie in your solution, the fix is pretty easy. You just need to set the SameSite attribute when the cookie gets created:

cookie.SameSite = SameSiteMode.None;
cookie.Secure = true;

Not all browsers are supporting the new SameSite attribute behavior yet though. Older browsers don’t recognize the None value for the SameSite attribute and they restrict the cookie to first-party domain requests instead.

In order to ensure that the new implementation doesn’t break in these browsers, Microsoft has recommended to detect the browser from where the web request has originated, parsing the user-agent value of the request, and to set the SameSite attribute only for compatible browsers.

How to adjust the authentication cookie (.AspNet.Cookies)

In my Sitecore solution, the authentication logic uses the out of the box Sitecore.Security.Authentication.AuthenticationManager.Login class to validate user’s credentials and authenticate the user. The code responsible to create the authentication cookie (.AspNet.Cookies) was not exposed and was not easily replaceable with a custom code implementation.

I decided instead to follow Microsoft’s recommendation on how to intercept and adjust cookies that we don’t have direct control on, using the Response.AddOnSendingHeaders event to intercept the writing of headers in a web response and to hook it up in the global.asax of the Sitecore solution.

The code below shows the implementation to adjust the authentication cookie in the Sitecore solution using the Response.AddOnSendingHeaders event:

public static void AdjustAuthenticationCookieSettings(object sender)
{
    HttpApplication application = sender as HttpApplication;
    if (application != null)
    {
        var userAgent = application.Context.Request.UserAgent;

        application.Response.AddOnSendingHeaders(context =>
        {
            var cookies = context.Response.Cookies;
            for (var i = 0; i < cookies.Count; i++)
            {
                var cookie = cookies[i];
                if (string.Equals(".AspNet.Cookies", cookie.Name, StringComparison.Ordinal))
                {
                    if (Utils.BrowserDetection.DisallowsSameSiteNone(userAgent))
                    {
                        cookie.SameSite = (SameSiteMode)(-1); // Unspecified
                    }
                    else
                    {
                        cookie.SameSite = SameSiteMode.None;
                    }
                    cookie.Secure = true;
                }
            }
        });      
    }
}

The SameSite attribute of the authentication cookie is left as unspecified for older browsers not supporting the new behavior.

Only on Content Delivery instances

The adjustment of the SameSite attribute for the authentication cookie fixes the issue experienced during the checkout process, but it breaks the authentication in the Sitecore platform using Identity Server. In order to avoid this issue, I restricted the application of the SameSite attribute adjustment to the Content Delivery instances only, using a custom application setting (called SameSite.AdjustValueInCookies in the code below) to enable or disable it.

This logic has been implemented in the Application_BeginRequest method on the global.asax file:

protected void Application_BeginRequest(object sender, EventArgs args)
{
    bool enforceSameSite = String.Compare(System.Web.Configuration.WebConfigurationManager.AppSettings.Get("SameSite.AdjustValueInCookies"), "true", StringComparison.InvariantCultureIgnoreCase) == 0;

    if (enforceSameSite)
    {
        SameSiteCookieRewriter.AdjustAuthenticationCookieSettings(sender);
    }
}

Conclusions

In this blog post I described my experience troubleshooting and fixing an issue in the checkout process of a Sitecore Commerce 9.1 solution affected by the SameSite attribute changes recently enforced by Google Chrome. If you have any questions please don’t hesitate to comment on this post.

Thank you for reading!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s