Payment fails on Drupal 9.2+ webforms for anonymous users
Drupal 9.2+ does not by default create a session for anonymous users. This results in a CSRF failure when submitting webforms which include a payment section.
CiviCRM ensures that a session is created for all form requests that it handles, but this does not include webform requests.
When responding to Ajax requests to /drupal/civicrm/payment/form initiated from a Drupal Webform,
Firewall::generateCSRFToken()
calls \CRM_Core_Config::singleton()->userSystem->getSessionId()
which in turn initializes the civicrm.tempstore.sessionid
$_SESSION array element. Normally, this would cause Drupal to save the session. However, the Ajax request is terminated by CiviCRM before the normal Drupal request flow is completed, and thus, the session is not saved and no session cookie is emitted.
One solution would be to modify CiviCRM core to save the session before terminating Ajax requests. But, since this failure is specific to the CSRF token generated by the Firewall extension, I think it is more appropriate to fix it there.
The following patch to Firewall::generateCSRFToken() (v1.5.9) resolves the problem. It was tested on Drupal 10.2.3 for both anonymous and logged-in users, and is believed to be compatible with earlier Drupal versions.
*** firewall/Civi/Firewall/Firewall_v1.5.9.php Mon Mar 25 09:18:23 2024
--- firewall/Civi/Firewall/Firewall.php Mon Mar 25 17:01:36 2024
***************
*** 277,282 ****
--- 277,294 ----
if (!empty($context)) {
\CRM_Core_Session::singleton()->set('csrf.' . $publicToken, $context, 'civi.firewall');
}
+
+ //Drupal 9.2+ does not by default create a session for anonymous users.
+ //While processing an Ajax request to /drupal/civicrm/payment/form initiated
+ //from a Drupal Webform, we therefore save the session to ensure that anonymous
+ //users receive a session cookie.
+ if (($_REQUEST["is_drupal_webform"] ?? '') == '1' &&
+ method_exists('\Drupal', 'request') &&
+ method_exists(\Drupal::request(), 'getSession') &&
+ method_exists(\Drupal::request()->getSession(), 'save')) {
+ \Drupal::request()->getSession()->save();
+ }
+
return $publicToken;
}
See duplicate issue stripe#473.