'Losing session data after POST from third party website
I have a Laravel site that redirects to a payment provider (external third party website). When the user completes their payment, they are redirected back to my site via a POST request.
The issue I'm having is that the user's session is lost when they return to the confirmation page.
I wondered if this was behaviour of PHP generally but it seems to be specific to Laravel.
I have checked my sessions.php config file and can confirm the following is set 'expire_on_close' => false,
.
I've created a very basic example of the issue below
My website (pre-sale)
Controller
public function redirect()
{
$user = Auth::user();
dd($user); // returns User model;
redirect()->away('http://www.example.com');
}
Payment provider website
Note, the request is sent via the application within the browser - not a callback. There is also no button. I just want to demonstrate the POST back to the Laravel site.
<html>
<head></head>
<body>
<form method="POST" action="http://www.example.com/payment/confirmation">
<input type="submit">
</form>
</body>
</html>
My website (post-sale)
Route
Route::post('/payment/confirmation', 'Payment\PaymentController@confirmation');
Controller
public function confirmation()
{
$user = Auth::user();
dd($user); // Returns null
}
I have added the path to the VerifyCsrfToken middleware's exception array. Is there anything within Laravel that would destroy the session on POSTing via an external website? I'm sure I'm missing something obvious. Thanks
Solution 1:[1]
I was able to resolve this issue by changing 'same_site' => 'lax',
to 'same_site' => null,
in config/session.php. This appears to be a new setting in Laravel 7+.
I'm not sure if there are any security implications caused by this change without further reading but this, for now, fixes the problem. It would be a nice feature to somehow whitelist certain domains.
Solution 2:[2]
Setting the 'same_site' => null might not work as expected again because Standards related to the Cookie SameSite attribute recently changed https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
The new default is now Lax, so if you set your same_site to null, the browser will assume the default of Lax which is not the policy you need.
You need to explicitly set 'same_site' => "none", a value of none for same_site requires your connection to be secure, hence, set 'secure' => true.
'same_site' => "none"
'secure' => true
Solution 3:[3]
In my testing, it seems the session is not actually destroyed. However, it is not loaded when receiving the external POST request, and the session will be destroyed if you allow the return to the main site to trigger a new session save. By sending a header()
redirect and terminating the process before a new session can be saved, it seems that it is possible to restore the existing session.
Okay, perhaps it's a bit gross?
Route::match(['get','post'],'/payment/confirmation','Payment\PaymentController@confirmation');
public function confirmation(Request $request)
{
// assert cookie or reload current URL
if (! $request->hasHeader('Cookie')) {
header('Location: '.url()->current());
exit;
}
$user = Auth::user();
dd($user); // user exists!
}
I'm not going to say this is a great solution. It does feel a bit hackish. Some additional testing may be required. But at first blush, it does seem to work — at least on my end. And maybe it gives some additional insight into what's actually going on behind the scenes.
Also, I'm not really sure whether you want to expose the payment confirmation page to a GET request.
But this was an interesting rabbit hole to go down for a bit.
Solution 4:[4]
You should keep your cookie session to 'lax', you don't want your cookies available on other sites, etc.
The fix as I have found is to pass true for remember when you do a login.
Example:
Auth::login(
user: $user,
remember: true
);
$request->session()->regenerate();
Your user will now be logged in indefinitely until they log out, ie going away from your site and returning via redirect headers and all that. I discovered this when using Passport to receive a callback for Oauth and I needed to log the user in at that point but redirect them to a subdomain for their tenant.
Normally you'll just want the session to be available across your root domain and subdomains like .mydomain.com
in your config/env.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | kinggs |
Solution 2 | |
Solution 3 | |
Solution 4 | Garrick Crouch |