Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Browser won't set ASP.NET_SessionId cookie on payment gateway's post request to our site

We're experiencing a weird problem with the payment process of our web application which results in loss of session data.

In this process, after our check-out page user is redirected to payment provider's page and redirected back to our site (to a url we specify) as soon as s/he's done there. This last redirect is done by browser's evaluation of the payment provider's html code which basically consists of a form that posts to our site and a few lines of javascript code that posts that form on page load. At this point browser makes the post request but does not set the "ASP.NET_SessionId" cookie which is present in the previous requests made to the exact same domain (our application's domain). What's more weird is that it sets another cookie we use named "AcceptCookie". It just simply chooses to drop "ASP.NET_SessionId" cookie.

To illustrate the situation I took some screenshots. (In these screenshots orange and green rectangles contain exactly the same value.)

  1. This is the request that made (to our application) when user presses "Check Out" button. After this request user is redirected to payment provider's page.

check-out request

  1. This is the final page that is served by payment provider after user is done there. As you can see it's just a simple form that is automatically posted to our domain on page load.

payment provider's final response

  1. But this post request does not include "ASP.NET_SessionId" cookie which results in acquiring a new session id and the loss of previous session data. And again, just "ASP.NET_SessionId" is missing, not the other one named "AcceptCookie".

post request that brings the user back to our site (made with javascript in the previous step)

Finally we figured that on the older versions of browsers this problem does not occur. On Firefox 52 it works like a charm but on Firefox 71 the above problem happens.

Any ideas?

Note: It's an ASP.NET MVC application with targetFramework="4.5.2"

Have a nice day.

like image 951
E. Özgür Avatar asked Dec 10 '19 14:12

E. Özgür


2 Answers

We figured it out.

Somehow "ASP.NET_SessionId" cookie's "SameSite" attribute defaults to "Lax" and this causes session cookie not being added to the request that made by payment gateway's javascript code.

We added following rule to the web.config file in order to override this value and set it to "None".

<configuration>
  <system.webServer>
    <rewrite>
      <outboundRules>
        <rule name="Add SameSite" preCondition="No SameSite">
          <match serverVariable="RESPONSE_Set_Cookie" pattern=".*" negate="false" />
          <action type="Rewrite" value="{R:0}; SameSite=None" />
          <conditions>
          </conditions>
        </rule>
        <preConditions>
          <preCondition name="No SameSite">
            <add input="{RESPONSE_Set_Cookie}" pattern="." />
            <add input="{RESPONSE_Set_Cookie}" pattern="; SameSite=None" negate="true" />
          </preCondition>
        </preConditions>
      </outboundRules>
    </rewrite>
  </system.webServer>
</configuration>

UPDATE 1: Just adding above configuration solved the problem for modern browsers but we realized that we were still having issues with older versions of Micosoft Edge and Internet Explorer.

So we needed to add cookieSameSite="None" attribute to sessionState node in web.config file.

<sessionState cookieSameSite="None" />

Be careful with this configuration change though, as older .net framework versions do not support it and cause your site to display error page.

By the way we're still having issues with browsers in IOS 12. But I think it's related to this confirmed bug

UPDATE 2: see zemien's answer for possible fix about IOS issue

UPDATE 3: By combining our findings with the suggestions in zemien's answer we've come up with the following rewrite rules. We've been using this configuration in production. But beware: it marks all the cookies with "SameSite:None" attribute for compatible browsers and excludes SameSite attribute, if exists, for incompatible browsers. It may seem complicated but I tried to explain via comment lines.

This is the FINAL configuration we use in production:

<configuration> 

  <system.webServer>

    <rewrite>

      <outboundRules>

        <preConditions>
          <!-- Browsers incompatible with SameSite=None -->
          <preCondition name="IncompatibleWithSameSiteNone" logicalGrouping="MatchAny">
            <add input="{HTTP_USER_AGENT}" pattern="(CPU iPhone OS 12)|(iPad; CPU OS 12)" />
            <add input="{HTTP_USER_AGENT}" pattern="(Chrome/5)|(Chrome/6)" />
            <add input="{HTTP_USER_AGENT}" pattern="( OS X 10_14).*(Version/).*((Safari)|(KHTML, like Gecko)$)" />
          </preCondition>

          <!-- Rest of the browsers are assumed to be compatible with SameSite=None -->
          <preCondition name="CompatibleWithSameSiteNone" logicalGrouping="MatchAll">
            <add input="{HTTP_USER_AGENT}" pattern="(CPU iPhone OS 12)|(iPad; CPU OS 12)" negate="true" />
            <add input="{HTTP_USER_AGENT}" pattern="(Chrome/5)|(Chrome/6)" negate="true" />
            <add input="{HTTP_USER_AGENT}" pattern="( OS X 10_14).*(Version/).*((Safari)|(KHTML, like Gecko)$)" negate="true" />
          </preCondition>

        </preConditions>

        <!-- Rule 1: Remove SameSite part from cookie for incompatible browsers if exists -->
        <rule name="Remove_SameSiteCookie_IfExists_ForLegacyBrowsers" preCondition="IncompatibleWithSameSiteNone">
          <match serverVariable="RESPONSE_Set-Cookie" pattern="(.*)(SameSite=.*)" />
          <action type="Rewrite" value="{R:1}" />
        </rule>

        <!-- Rule 2: Override SameSite's value to None if exists, for compatible browsers -->
        <rule name="Override_SameSiteCookie_IfExists_ForModernBrowsers" preCondition="CompatibleWithSameSiteNone">
          <match serverVariable="RESPONSE_Set-Cookie" pattern="(.*)(SameSite=.*)" />
          <action type="Rewrite" value="{R:1}; SameSite=None" />
        </rule>

        <!-- Rule 3: Add SameSite attribute with the value None if it does not exists, for compatible browsers -->
        <rule name="Add_SameSiteCookie_IfNotExists_ForModernBrowsers" preCondition="CompatibleWithSameSiteNone">
          <match serverVariable="RESPONSE_Set-Cookie" pattern=".*"/>
          <!-- Condition explanation: Cookie data contains some string value but does not contain SameSite attribute -->
          <conditions logicalGrouping="MatchAll">
            <add input="{R:0}" pattern="^(?!\s*$).+"/>
            <add input="{R:0}" pattern="SameSite=.*" negate="true"/>
          </conditions>
          <action type="Rewrite" value="{R:0}; SameSite=None" />
        </rule>

      </outboundRules>

    </rewrite>    

  </system.webServer>  

</configuration>
like image 140
E. Özgür Avatar answered Nov 19 '22 00:11

E. Özgür


I modified upon several SO answers to come up with this URL rewrite that adds SameSite=None to session cookies, and also remove SameSite=None from all cookies for most incompatible browsers. The aim of this rewrite is to preserve the "legacy" behaviour pre-Chrome 80.

Full write-up in my Coder Frontline blog:

<rewrite>
  <outboundRules>
    <preConditions>
      <!-- Checks User Agent to identify browsers incompatible with SameSite=None -->
      <preCondition name="IncompatibleWithSameSiteNone" logicalGrouping="MatchAny">
        <add input="{HTTP_USER_AGENT}" pattern="(CPU iPhone OS 12)|(iPad; CPU OS 12)" />
        <add input="{HTTP_USER_AGENT}" pattern="(Chrome/5)|(Chrome/6)" />
        <add input="{HTTP_USER_AGENT}" pattern="( OS X 10_14).*(Version/).*((Safari)|(KHTML, like Gecko)$)" />
      </preCondition>
    </preConditions>

    <!-- Adds or changes SameSite to None for the session cookie -->
    <!-- Note that secure header is also required by Chrome and should not be added here -->
    <rule name="SessionCookieAddNoneHeader">
      <match serverVariable="RESPONSE_Set-Cookie" pattern="((.*)(ASP.NET_SessionId)(=.*))(SameSite=.*)?" />
      <action type="Rewrite" value="{R:1}; SameSite=None" />
    </rule>

    <!-- Removes SameSite=None header from all cookies, for most incompatible browsers -->
    <rule name="CookieRemoveSameSiteNone" preCondition="IncompatibleWithSameSiteNone">
      <match serverVariable="RESPONSE_Set-Cookie" pattern="(.*)(SameSite=None)" />
      <action type="Rewrite" value="{R:1}" />
    </rule>
  </outboundRules>
</rewrite>

This should work for most ASP .Net and ASP .Net Core applications, although newer Frameworks have proper code and config options to let you control this behaviour. I would recommend researching all the options available to you before using my rewrite above.

like image 39
zemien Avatar answered Nov 19 '22 02:11

zemien