If you have a website where you are implementing 3D Secure payments, you may find that you have an issue where on receipt of the payment setup confirmation the users Session ID has changed with no apparent cause.
Lets have a quick run through of the payment process in this scenario (this is roughly how SagePay and WorldPay both work):
- User completes payment details on your site (some wizardry normally happens at this point with an iFrame for the card number to maintain PCI compliance) and the form is submitted to your server
- You server call's an API from the payment gateway to setup a 3D Secure payment. It passes the users Session ID along with all the payment details.
- Payment gateway responds with a URL for you to redirect the user too by posting a form to it. You do this, likely in an iFrame so that the page still looks like your website (otherwise it's a very ugly page).
- User may or may not get prompted by some sort of authentication by the bank. This could be something like receiving a text message with a code to enter.
- When authenticated the user is sent back to your website (in the iFrame) with a post request.
- Your server picks out the form details from the request and then calls an API to complete the transaction. In this API call you send the Session ID so that the payment gateway can validate it is the same as the one at the start of the process.
- Confirmation shown to the user.
In 2020 a change made to how cookies function in browsers to defend against cross site scripting. Troy Hunt has a brilliant explanation of the issue with how cookies used to work and how this has changed here. I'm going to try a much shorter explanation;
When a request is made from a browser, as part of the request all the cookie values for that domain are sent with the request. This will include one for the Session ID. The theory here is that because the cookies are only being sent to the domain which set them in the first place, then information is only being shared back with the place that set it to begin with, which is therefor safe.
However the workings of the internet and what domain a button click might call isn't overly obvious to most people. So what if clicking a link on one site causes the user to be redirected to another site? Answer: all the cookies are still sent. The same thing happens if a form is posted from one site to another site. The problem here is that if you are authenticated on the other website, then its possible for a cross site scripting attack to be using your session via your browser without you even realising you were on the site again. This is why it's always a good idea to log out of websites!
The introduction of the same site policy changes how cookies work with three options:
- None: which is what the browsers used to do. i.e. send all the cookies with cross-origin request
- Lax: some limits on sending cookies with cross-origin request
- Strict: tight limits on sending cookies with cross-origin request
None is sending everything and a strict policy will basically stop cookies from being sent when they have a cross-origin request.
A Lax policy is slightly more interesting though, because it depends if the request is a GET or a POST. If it is a GET then the cookies will still be sent, which means that if you follow a link from another site or a search engine your cookies will still be sent to the site. However a POST (like what the 3D Secure page is doing) will no longer send the cookies.
If the policy isn't set, then Lax is used as the default.
So why is my Session ID changing?
You can find more about this here. In everything older the policy wont be set and will default to Lax.
The solution I went with was to have a page that the users is redirected back to from the payment gateway that does nothing other than re-submit the values from the payment gateway to the site again. This way I can be sure of what form data is being posted to the site, and when I re-post it, it is a same site post and not a cross-origin which means the Session ID cookie will be sent.
To stop my initial page setting a new session cookie (which it will try to do because it won't recieve one), I use the IIS URL Rewrite module to strip out the set cookie response headers from the page.
You can do this with an outbound rule as follows:
With this solution the cookies are still secure as the policy is set to Lax and I can take payments using 3D Secure which will soon become a requirement.