Commit d28b900ecf for woocommerce
commit d28b900ecf82c91fde8728e7645415141bb32d99
Author: Eric Binnion <ericbinnion@gmail.com>
Date: Thu Feb 5 21:02:16 2026 -0500
Store API: Address bug where non wp-json did not respect cart-token (#62973)
* Store API: Address bug where non wp-json did not respect cart-token
Co-authored-by: Radoslav Georgiev <rageorgiev@gmail.com>
diff --git a/plugins/woocommerce/changelog/62973-fix-store-api-cart-token-rest-route b/plugins/woocommerce/changelog/62973-fix-store-api-cart-token-rest-route
new file mode 100644
index 0000000000..855dad32da
--- /dev/null
+++ b/plugins/woocommerce/changelog/62973-fix-store-api-cart-token-rest-route
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Ensure Store API cart sessions restore correctly for rest_route requests with Cart-Token.
diff --git a/plugins/woocommerce/src/StoreApi/Authentication.php b/plugins/woocommerce/src/StoreApi/Authentication.php
index 1204650a8d..f70ab87af7 100644
--- a/plugins/woocommerce/src/StoreApi/Authentication.php
+++ b/plugins/woocommerce/src/StoreApi/Authentication.php
@@ -40,6 +40,26 @@ class Authentication {
return $allowed_headers;
}
+ /**
+ * Use the Store API session handler when a valid Cart-Token is present.
+ *
+ * @since 10.6.0
+ * @param string $handler Session handler class name.
+ * @return string
+ */
+ public function maybe_use_store_api_session_handler( $handler ): string {
+ if ( ! WC()->is_store_api_request() && ! $this->has_store_api_route_as_get_parameter() ) {
+ return $handler;
+ }
+
+ $cart_token = wc_clean( wp_unslash( $_SERVER['HTTP_CART_TOKEN'] ?? '' ) );
+ $cart_token = is_string( $cart_token ) ? $cart_token : '';
+ if ( $cart_token && CartTokenUtils::validate_cart_token( $cart_token ) ) {
+ return SessionHandler::class;
+ }
+ return $handler;
+ }
+
/**
* Expose Store API headers in CORS responses.
* We're explicitly exposing the Cart-Token, not the nonce. Only one of them is needed.
@@ -96,6 +116,23 @@ class Authentication {
return $served;
}
+ /**
+ * Checks if the request has a store API route as a GET `rest_route` parameter.
+ *
+ * @since 10.6.0
+ * @return bool
+ */
+ protected function has_store_api_route_as_get_parameter(): bool {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Store API
+ if ( ! isset( $_GET['rest_route'] ) || ! is_string( $_GET['rest_route'] ) ) {
+ return false;
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Store API context check.
+ $rest_route = rawurldecode( esc_url_raw( wp_unslash( $_GET['rest_route'] ) ) );
+ return 0 === strpos( $rest_route, '/wc/store/' );
+ }
+
/**
* Is the request a preflight request? Checks the request method
*
diff --git a/plugins/woocommerce/src/StoreApi/StoreApi.php b/plugins/woocommerce/src/StoreApi/StoreApi.php
index dccea2dd87..50a9572b9b 100644
--- a/plugins/woocommerce/src/StoreApi/StoreApi.php
+++ b/plugins/woocommerce/src/StoreApi/StoreApi.php
@@ -20,6 +20,15 @@ final class StoreApi {
* Init and hook in Store API functionality.
*/
public function init() {
+ /**
+ * Authentication instance.
+ *
+ * @var Authentication $authentication
+ */
+ $authentication = self::container()->get( Authentication::class );
+
+ add_filter( 'woocommerce_session_handler', array( $authentication, 'maybe_use_store_api_session_handler' ), 0 );
+
add_action(
'rest_api_init',
function () {
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Cart.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Cart.php
index 47a75ee757..b3b4c295f1 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Cart.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Cart.php
@@ -7,7 +7,9 @@ namespace Automattic\WooCommerce\Tests\Blocks\StoreApi\Routes;
use Automattic\WooCommerce\Tests\Blocks\Helpers\FixtureData;
use Automattic\WooCommerce\Tests\Blocks\Helpers\ValidateSchema;
+use Automattic\WooCommerce\StoreApi\Authentication;
use Automattic\WooCommerce\StoreApi\SessionHandler;
+use Automattic\WooCommerce\StoreApi\Utilities\CartTokenUtils;
use Automattic\WooCommerce\StoreApi\Utilities\JsonWebToken;
use Spy_REST_Server;
use Automattic\WooCommerce\Enums\ProductStockStatus;
@@ -613,6 +615,84 @@ class Cart extends ControllerTestCase {
);
}
+ /**
+ * Test Store API uses SessionHandler when Cart-Token is present via REQUEST_URI.
+ */
+ public function test_store_api_uses_session_handler_for_cart_token() {
+ $customer_id = (string) wc()->session->get_customer_id();
+ $token = CartTokenUtils::get_cart_token( $customer_id );
+
+ // Preserve globals.
+ $old_server = $_SERVER;
+
+ try {
+ // Simulate a Store API request with valid Cart-Token.
+ $_SERVER['REQUEST_URI'] = '/' . rest_get_url_prefix() . '/wc/store/v1/cart';
+ $_SERVER['HTTP_CART_TOKEN'] = $token;
+
+ $authentication = new Authentication();
+ $result = $authentication->maybe_use_store_api_session_handler( 'WC_Session_Handler' );
+
+ $this->assertSame( SessionHandler::class, $result );
+ } finally {
+ // Restore globals.
+ $_SERVER = $old_server;
+ }
+ }
+
+ /**
+ * Test Store API uses SessionHandler when rest_route GET parameter is present.
+ */
+ public function test_rest_route_get_parameter_uses_store_api_session_handler() {
+ $customer_id = (string) wc()->session->get_customer_id();
+ $token = CartTokenUtils::get_cart_token( $customer_id );
+
+ // Preserve globals.
+ $old_get = $_GET; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Test context.
+ $old_server = $_SERVER;
+
+ try {
+ // Simulate a Store API request via GET parameter with valid Cart-Token.
+ $_GET['rest_route'] = '/wc/store/v1/cart';
+ $_SERVER['HTTP_CART_TOKEN'] = $token;
+
+ $authentication = new Authentication();
+ $result = $authentication->maybe_use_store_api_session_handler( 'WC_Session_Handler' );
+
+ $this->assertSame( SessionHandler::class, $result );
+ } finally {
+ // Restore globals.
+ $_GET = $old_get;
+ $_SERVER = $old_server;
+ }
+ }
+
+ /**
+ * Test non-Store API routes do not switch to Store API session handler.
+ */
+ public function test_non_store_api_route_does_not_use_store_api_session_handler() {
+ $customer_id = (string) wc()->session->get_customer_id();
+ $token = CartTokenUtils::get_cart_token( $customer_id );
+
+ // Preserve globals.
+ $old_server = $_SERVER;
+
+ try {
+ // Simulate a non-Store API request (even with valid Cart-Token).
+ $_SERVER['REQUEST_URI'] = '/' . rest_get_url_prefix() . '/wp/v2/posts';
+ $_SERVER['HTTP_CART_TOKEN'] = $token;
+
+ $authentication = new Authentication();
+ $result = $authentication->maybe_use_store_api_session_handler( 'WC_Session_Handler' );
+
+ // Should return the default handler for non-Store API routes.
+ $this->assertSame( 'WC_Session_Handler', $result );
+ } finally {
+ // Restore globals.
+ $_SERVER = $old_server;
+ }
+ }
+
/**
* Test that cart GET endpoint sends Cache-Control headers.
*/