We have a customer who is using Sitecore and the SagePay Pi service to take credit card payments. They were using a multi-page Sitecore Forms form, and weirdly their confirmation emails couldn’t use field values from the first pages of the form. Eventually, we found that the problem was due to the the user’s session being lost, but only if the user was using 3D-secure, and a recent browser. Here’s why…
We had tested the form and emails during development, so we were surprised that fields were unavailable to be used in emails. Re-testing this process showed that the FormSubmitContext didn’t have fields from the earlier pages of the forms when 3D secure authentication was used. But why?
Well, the form data from the earlier pages was being stored in the user’s session. On submission, it would be loaded from the session to populate the FormSubmitContext. However, those sessions appeared to be getting lost.
Now, we did have a problem for a while with the user’s session only being available for 1 minute – which is kind of short – but this type of error seems to have been transient. We suspect that it might relate to robots detection, which uses sessions of 1 minute. We were unable to recreate this problem, and ultimately, it turned out there was a different issue.
Getting 3D-secure results
3D-secure use an IFrame. Some details about the transaction are passed into the IFrame (as GET parameters) and then the IFrame window is directed to a bank where some additional authentication may be applied – potentially entering a password or a code that they send to your phone. What authentication that is varies by bank.
At the end of that process, the IFrame will load a page in SagePay PI that will POST itself to an endpoint in your application. That POST will contain details about the transaction, its status, and so on. This was where the problem lies.
This POST from SagePay PI is cross-site. It’s posting from SagePay’s website to our Sitecore site. That used to be fine, but changes to the SameSite settings on cookies in Chrome mean that it’s not okay anymore.
SameSite cookie settings are intended to block a type of attack called Cross Site Request Forgery (CSRF). This is where a third-party site would make requests of a site you were logged in on. Because you were logged in and had session cookies, they would be sent with the requests from the malicious site, and as far as the site you’re logged in on is concerned, you’re making those requests. This is why banks are so keen on getting you to log-out when you’re finished.
The SameSite cookie settings give you a number of options that help you prevent this.
- If you set a cookie with the “Strict” flag, your browser will only send that cookie if you’re on a page on the site you’re logged in on. Cross-site requests will not send that cookie. This blocks CSRF.
- If you set a cookie with the “None” flag, your browser will happily send that cookie, even on cross-site requests.
- If you set a cookie with the “Lax” flag, your browser will send the cookie with navigations – i.e. GET requests. It won’t be sent by POST actions. This offers some protection against someone using CSRF – you can’t submit forms with it – but still allows third-party cookies to be used for tracking users.
Recently Chrome was changed so that if no SameSite setting is defined, it will be marked as “Lax” by default. On our customer’s system this included both the ASP.NET_SessionId and the SC_GLOBAL_ANALYTICS_COOKIE. This meant that the POST from SagePay was arriving without either of those cookies – so Sitecore was kindly setting them in the response, which overwrote the values for those cookies that the visitor’s browser already had.
We can see this in request from the page. Before the 3D-secure POST request from SagePay, we have one session ID:
But in the response to the POST request from SagePay we can see 2 new cookies being set:
Unfortunately, the ASP.NET_SessionId and SC_GLOBAL_ANALYTICS_COOKIE link a user to their private and shared session states. Therefore, changing those cookies causes the session containing the previous pages form fields from to be lost, and therefore the fields were not available for the EXM email.
A simple test was to, in a browser, check the ASP.NET_SessionId cookie before and after a 3D-secure payment. Here’s before…
… and after the session ID had changed!
Okay, so how to fix that? There are a few options:
- The ASP.NET_SessionId can be configured to have a default SameSite value of “None”. However, that doesn’t fix the SC_GLOBAL_ANALYTICS_COOKIE.
- Alternatively, you can set all cookies to have a default SameSite value of “None”. This means that both session cookies will be sent with cross-site requests. It’s pretty easy to configure. However, that’s what you’re trying to prevent with SameSite settings, though, so that might not be ideal.
- Another possibility would be to prevent the set-cookie header being sent to the visitor’s browser. We configured the UrlRewrite module in IIS to strip those headers out of responses from the endpoint that SagePay was POSTing to.
Once we did this our session cookies stopped being reset and we were able to use fields from earlier pages in the Sitecore Forms form in the EXM mail we were sending out.
Now, this is quite a recent feature of web browsers; not all browsers support it. Chrome introduced it in Chrome 80, so about February 2020. I expect Firefox will do similar soon.
All of which goes to show why the changes to how Chrome handles SameSite settings for cookies is a big deal, and attention needs to be paid to it in the future.