Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What's the proper way to redirect from external payment Gateway with POST redirect and still use SameSite: Lax? #38889

Open
1 of 5 tasks
ioweb-gr opened this issue Jun 30, 2024 · 10 comments
Labels
Area: Payments Component: Payment Issue: Confirmed Gate 3 Passed. Manual verification of the issue completed. Issue is confirmed Priority: P3 May be fixed according to the position in the backlog. Progress: ready for dev Reported on 2.4.x Indicates original Magento version for the Issue report. Reproduced on 2.4.x The issue has been reproduced on latest 2.4-develop branch Triage: Dev.Experience Issue related to Developer Experience and needs help with Triage to Confirm or Reject it

Comments

@ioweb-gr
Copy link
Contributor

ioweb-gr commented Jun 30, 2024

Summary

I've read many of the SameSite issues and possible solutions / workarounds on the repo issues, however so far the only real way of making this work is setting SameSite to None.

My question is about how to make it work with Lax mode and how Paypal integration works.

I've been checking the paypal module and it seems to preserve the session for the logged in user, it uses this plugin

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magento\Paypal\Plugin;

use Magento\Framework\App\Request\Http;
use Magento\Framework\Session\SessionStartChecker;

/**
 * Intended to preserve session cookie after submitting POST form from PayPal to Magento controller.
 */
class TransparentSessionChecker
{
    /**
     * @var string[]
     */
    private $disableSessionUrls = [
        'paypal/transparent/redirect',
        'paypal/payflowadvanced/returnUrl',
        'paypal/payflow/returnUrl',
        'paypal/hostedpro/return',
    ];

    /**
     * @var Http
     */
    private $request;

    /**
     * @param Http $request
     */
    public function __construct(
        Http $request
    ) {
        $this->request = $request;
    }

    /**
     * Prevents session starting while instantiating PayPal transparent redirect controller.
     *
     * @param SessionStartChecker $subject
     * @param bool $result
     * @return bool
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function afterCheck(SessionStartChecker $subject, bool $result): bool
    {
        if ($result === false) {
            return false;
        }

        foreach ($this->disableSessionUrls as $url) {
            if (strpos((string)$this->request->getPathInfo(), $url) !== false) {
                return false;
            }
        }

        return true;
    }
}

This effectively makes the session not be destroyed and the user stays logged in.

However on the initial controller which handles the response in my case

cardlink_checkout/payment/response

When fetching the CheckoutSession via Factory or via Proxy or Plain with the class through DI, it's an empty session.

I've read further into the paypal integration and saw that it uses an intermediate redirect by intercepting the initial POST request

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Magento\Paypal\Controller\Transparent;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\CsrfAwareActionInterface;
use Magento\Framework\App\Request\InvalidRequestException;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\View\Result\LayoutFactory;
use Magento\Payment\Model\Method\Logger;
use Magento\Paypal\Model\Payflow\Transparent;

/**
 * Class for redirecting the Paypal response result to Magento controller.
 */
class Redirect extends Action implements CsrfAwareActionInterface, HttpPostActionInterface
{
    /**
     * @var LayoutFactory
     */
    private $resultLayoutFactory;

    /**
     * @var Transparent
     */
    private $transparent;

    /**
     * @var Logger
     */
    private $logger;

    /**
     * @param Context $context
     * @param LayoutFactory $resultLayoutFactory
     * @param Transparent $transparent
     * @param Logger $logger
     */
    public function __construct(
        Context $context,
        LayoutFactory $resultLayoutFactory,
        Transparent $transparent,
        Logger $logger
    ) {
        $this->resultLayoutFactory = $resultLayoutFactory;
        $this->transparent = $transparent;
        $this->logger = $logger;

        parent::__construct($context);
    }

    /**
     * @inheritdoc
     */
    public function createCsrfValidationException(
        RequestInterface $request
    ): ?InvalidRequestException {
        return null;
    }

    /**
     * @inheritdoc
     */
    public function validateForCsrf(RequestInterface $request): ?bool
    {
        return true;
    }

    /**
     * Saves the payment in quote
     *
     * @return ResultInterface
     * @throws LocalizedException
     */
    public function execute()
    {
        $gatewayResponse = (array)$this->getRequest()->getPostValue();
        $this->logger->debug(
            ['PayPal PayflowPro redirect:' => $gatewayResponse],
            $this->transparent->getDebugReplacePrivateDataKeys(),
            $this->transparent->getDebugFlag()
        );

        $resultLayout = $this->resultLayoutFactory->create();
        $resultLayout->addDefaultHandle();
        $resultLayout->getLayout()->getUpdate()->load(['transparent_payment_redirect']);

        return $resultLayout;
    }
}

Which effectively renders a form which submits again the same data but from the same domain

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

/** @var \Magento\Payment\Block\Transparent\Redirect $block */
$params = $block->getPostParams();
$redirectUrl = $block->getRedirectUrl();
?>
<html>
<head></head>
<body onload="document.forms['proxy_form'].submit()">
<form id="proxy_form" action="<?= $block->escapeUrl($redirectUrl) ?>"
      method="POST" hidden enctype="application/x-www-form-urlencoded" class="no-display">
    <?php foreach ($params as $name => $value):?>
        <input value="<?= $block->escapeHtmlAttr($value) ?>" name="<?= $block->escapeHtmlAttr($name) ?>" type="hidden"/>
    <?php endforeach?>
</form>
</body>
</html>

the URL is declared in di.xml

<?xml version="1.0"?>
<!--
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd">
    <container name="root" label="Root">
        <block class="Magento\Payment\Block\Transparent\Redirect" name="transparent_redirect" template="Magento_Payment::transparent/redirect.phtml">
            <arguments>
                <argument name="route_path" xsi:type="string">paypal/transparent/response</argument>
            </arguments>
        </block>
    </container>
</layout>

However when reaching the response controller equivalent in the module I'm trying to patch, I still can't get the CheckoutSession properly and it's entirely empty.

Furthermore, when setting the data to it for the LastSuccessQuoteId and redirecting to checkout/onepage/success controller, I notice that there, the checkoutSession is restored entirely to the previous state, before my modification. Thus because the LastSuccessQuoteId is not there in that state, I'm redirected to cart instead.

So I'm assuming here that something extra is being done in the paypal gateway to retain the actual CheckoutSession and modify that one instead of creating a new empty one.

However I'm unable to find any type of documentation regarding this. In the official docs, there's no mention of how to tackle SameSite and also it is left in the responsibility of each payment gateway to fix it. However this is a problem for Magento itself and us developers trying to fix the gateway modules without nuking the settings to make the cookies set Samesite to "None"

As far as I can see in the network tab, the PHPSESSID is there in all requests, but the cookie isn't.

On redirecting to payment provider

image
image

On redirecting to the response handler which creates the "proxy" form as expected nothing is there
image

On the actual processor everything is there except the PHPSESSID cookie for some reason in the response and in the request the PHPSESS ID is actually there
image
image

And on the checkout/onepage/success controller which can also see the original CheckoutSession I can see everything there as well.

image

So my question is what's the missing step to actually fetch the proper CheckoutSession after the "proxy" form redirects to the actual processor.

Assuming of course what paypal module shows in transparent/redirect classes / phtml files is the correct approach.

If not, what's the correct approach to achieve this while still using SameSite = Lax.

Examples

cardlink.zip

here's an example module provided by Cardlink a major payment gateway in Greece.

Their proposal was to change SameSite to "None" as well.

I'm particularly curious as to why the information for the session is transferred properly when redirecting from the responseProcessor to checkout/onepage/success controller on redirect 2 while not transferred properly when redirecting from the response controller to the responseProcessor on redirect 1 because if the information is there in redirect 2, why wouldn't it be available in redirect 1?

Any help is appreciated

Proposed solution

No response

Release note

No response

Triage and priority

  • Severity: S0 - Affects critical data or functionality and leaves users without workaround.
  • Severity: S1 - Affects critical data or functionality and forces users to employ a workaround.
  • Severity: S2 - Affects non-critical data or functionality and forces users to employ a workaround.
  • Severity: S3 - Affects non-critical data or functionality and does not force users to employ a workaround.
  • Severity: S4 - Affects aesthetics, professional look and feel, “quality” or “usability”.
@ioweb-gr ioweb-gr added the Triage: Dev.Experience Issue related to Developer Experience and needs help with Triage to Confirm or Reject it label Jun 30, 2024
Copy link

m2-assistant bot commented Jun 30, 2024

Hi @ioweb-gr. Thank you for your report.
To speed up processing of this issue, make sure that the issue is reproducible on the vanilla Magento instance following Steps to reproduce. To deploy vanilla Magento instance on our environment, Add a comment to the issue:


Join Magento Community Engineering Slack and ask your questions in #github channel.
⚠️ According to the Magento Contribution requirements, all issues must go through the Community Contributions Triage process. Community Contributions Triage is a public meeting.
🕙 You can find the schedule on the Magento Community Calendar page.
📞 The triage of issues happens in the queue order. If you want to speed up the delivery of your contribution, join the Community Contributions Triage session to discuss the appropriate ticket.

@engcom-Bravo engcom-Bravo added the Reported on 2.4.x Indicates original Magento version for the Issue report. label Jul 1, 2024
@engcom-Hotel engcom-Hotel self-assigned this Jul 2, 2024
Copy link

m2-assistant bot commented Jul 2, 2024

Hi @engcom-Hotel. Thank you for working on this issue.
In order to make sure that issue has enough information and ready for development, please read and check the following instruction: 👇

  • 1. Verify that issue has all the required information. (Preconditions, Steps to reproduce, Expected result, Actual result).
  • 2. Verify that issue has a meaningful description and provides enough information to reproduce the issue.
  • 3. Add Area: XXXXX label to the ticket, indicating the functional areas it may be related to.
  • 4. Verify that the issue is reproducible on 2.4-develop branch
    Details- Add the comment @magento give me 2.4-develop instance to deploy test instance on Magento infrastructure.
    - If the issue is reproducible on 2.4-develop branch, please, add the label Reproduced on 2.4.x.
    - If the issue is not reproducible, add your comment that issue is not reproducible and close the issue and stop verification process here!
  • 5. Add label Issue: Confirmed once verification is complete.
  • 6. Make sure that automatic system confirms that report has been added to the backlog.

@engcom-Hotel
Copy link
Contributor

Hello @ioweb-gr,

Thanks for the report and collaboration!

We have gone through with your issue, in your case, you're trying to make it work with "Lax" mode. However, the issue is that the session cookie is not sent when the user is redirected back to your site from the payment gateway. This is because the browser sees this as a cross-site request and, due to the "Lax" setting, does not include the session cookie in the request. As a result, Magento can't associate the request with a session and creates a new, empty session.

The PayPal module gets around this by using an intermediate page that submits a form to the final destination. This form submission is seen as a same-site request by the browser, so the session cookie is included in the request.

But it seems to us an expected behavior and like PayPal handles the situation, Cardlink also handles similarly. Can you please raise the same issue with them?

Please let us know if we missed anything here.

Thanks

@engcom-Hotel engcom-Hotel added Issue: needs update Additional information is require, waiting for response and removed Issue: ready for confirmation labels Jul 2, 2024
@ioweb-gr
Copy link
Contributor Author

ioweb-gr commented Jul 2, 2024

Hi @engcom-Hotel

first of all let me clarify that this module is the attempt I made to handle it like PayPal. So it's a modified version of the original. The original can be found here https://github.com/Cardlink-SA/cardlink-payment-gateway-magento2x and as you said is suffering from SameSite issue.

Basically:

In the original case

User is redirected to the payment gateway and pays
The payment gateway redirects him to the magento site and due to Lax setting, the cookie is not there so the new session is created

In my modified version

I tried to replicate what Paypal does, by using the transparent redirect you mention that creates an internal page, a form with the orignal post data, which is automatically submitted to the processor.

I'm not sure if I've misinterpreted something from the Paypal module, but it seemed to me that's the way it handles it as you also mention

The PayPal module gets around this by using an intermediate page that submits a form to the final destination. This form submission is seen as a same-site request by the browser, so the session cookie is included in the request.

However in my module as you can see even if the process looks identical to paypal, again I'm facing the issue that on the actual ResponseProcessor controller which gets triggered after the second submission (from the same site) triggered by the ResponseController, still I don't have the proper session.

Basically the checkout session inside ResponseProcessor which is invoked after the automatic submission of the form rendered by the Response controller which processes the original request from Cardlink is still empty.

However after ResponseProcessor is done and redirects to the success page, the session is there properly.

To visualize this

cardlink

According to my understanding during the internal redirect from one magento controller to the other, the session should have been recovered but it isn't until the final GET request is done.

@engcom-Hotel
Copy link
Contributor

Thanks @ioweb-gr for the clarification!

We are confirming this issue for further processing.

Thanks

@engcom-Hotel engcom-Hotel added Component: Payment Issue: Confirmed Gate 3 Passed. Manual verification of the issue completed. Issue is confirmed Reproduced on 2.4.x The issue has been reproduced on latest 2.4-develop branch Priority: P3 May be fixed according to the position in the backlog. Area: Payments and removed Issue: needs update Additional information is require, waiting for response labels Jul 3, 2024
@github-jira-sync-bot
Copy link

✅ Jira issue https://jira.corp.adobe.com/browse/AC-12330 is successfully created for this GitHub issue.

Copy link

m2-assistant bot commented Jul 3, 2024

✅ Confirmed by @engcom-Hotel. Thank you for verifying the issue.
Issue Available: @engcom-Hotel, You will be automatically unassigned. Contributors/Maintainers can claim this issue to continue. To reclaim and continue work, reassign the ticket to yourself.

@ioweb-gr
Copy link
Contributor Author

ioweb-gr commented Jul 3, 2024

Hi @engcom-Hotel thank you for confirming the issue.

Do you think you can ask the core developers who created the paypal module to provide some insight as to what extra does the PayPal module do to retain the session?

This is a very important issue since we are prevented from integrating with payment gateways.

@ioweb-gr
Copy link
Contributor Author

Any update on this issue and what paypal does different to handle this use case?>

@ioweb-gr
Copy link
Contributor Author

ioweb-gr commented Oct 19, 2024

I've done some further digging by adding a log in each step on a similar integration suffering from the same issue. It seems that the session_id exists in $_COOKIE with the same ID

[2024-10-19T09:13:30.948621+00:00] ioweb_logger.INFO: Session id from cookie: gro6j5l5dgndtqc8g0vt0qm1dl [] []
[2024-10-19T09:13:30.949078+00:00] ioweb_logger.INFO: Redirecting to PiraeusBank Gateway for quote ID: 21753 [] []
[2024-10-19T09:14:01.669185+00:00] ioweb_logger.INFO: FAIL TRANSPARENT PROCESSOR: Session id from cookie: gro6j5l5dgndtqc8g0vt0qm1dl [] []

So basically even though the cookie exists, and the PHPSESSID is set, the magento session is empty when it reaches the final processing URL

image

But at the same time php's session_id function returns nothing after the redirect. So the session is indeed lost right after the redirect from the payment gateway to the magento url

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Payments Component: Payment Issue: Confirmed Gate 3 Passed. Manual verification of the issue completed. Issue is confirmed Priority: P3 May be fixed according to the position in the backlog. Progress: ready for dev Reported on 2.4.x Indicates original Magento version for the Issue report. Reproduced on 2.4.x The issue has been reproduced on latest 2.4-develop branch Triage: Dev.Experience Issue related to Developer Experience and needs help with Triage to Confirm or Reject it
Projects
Status: Ready for Development
Development

No branches or pull requests

4 participants