Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Store API - Cart JWT tokens/session handling #5953

Merged
merged 61 commits into from
Oct 3, 2022

Conversation

mikejolley
Copy link
Member

@mikejolley mikejolley commented Feb 25, 2022

POC for #5683 Includes changes from #6020

Returns a header containing a Cart-Token valid for 48h, which can be added as a request header to load a specific cart.

Works by extending the WC_Session class to intercept requests with a valid header.

Testing

Automated Tests

  • Changes in this PR are covered by Automated Tests.
    • Unit tests
    • E2E tests

User Facing Testing

  1. Using a REST API client
  2. Disabling basic auth
  3. Adding something to the cart via POST /wc/store/v1/cart/add-item. Note down the value of the Cart-Token header.
  4. Deleting or disabling cookies before requesting GET /wc/store/v1/cart
  5. No items
  6. Repeat this request with a header called Cart-Token and the value you noted earlier.
  7. Cart response has items!
  • Do not include in the Testing Notes

WooCommerce Visibility

  • WooCommerce Core
  • Feature plugin
  • Experimental

Performance Impact

Changelog

StoreApi requests will return a Cart-Token header that can be used to retrieve the cart from the corresponding session via GET /wc/store/v1/cart

@mikejolley mikejolley added the focus: rest api Work impacting REST api routes. label Feb 25, 2022
@rubikuserbot rubikuserbot requested review from a team and tarhi-saad and removed request for a team February 25, 2022 17:31
@github-actions
Copy link
Contributor

github-actions bot commented Feb 25, 2022

Size Change: 0 B

Total Size: 916 kB

ℹ️ View Unchanged
Filename Size
build/active-filters-frontend.js 7.62 kB
build/active-filters.js 8.3 kB
build/all-products-frontend.js 26.5 kB
build/all-products.js 33.6 kB
build/all-reviews.js 7.79 kB
build/attribute-filter-frontend.js 22.4 kB
build/attribute-filter.js 13.3 kB
build/blocks-checkout.js 17.5 kB
build/cart-blocks/cart-accepted-payment-methods-frontend.js 1.39 kB
build/cart-blocks/cart-cross-sells-frontend.js 253 B
build/cart-blocks/cart-cross-sells-products--product-add-to-cart-frontend.js 5.63 kB
build/cart-blocks/cart-cross-sells-products-frontend.js 4.66 kB
build/cart-blocks/cart-express-payment--checkout-blocks/express-payment-frontend.js 5.12 kB
build/cart-blocks/cart-express-payment-frontend.js 798 B
build/cart-blocks/cart-items-frontend.js 299 B
build/cart-blocks/cart-line-items--mini-cart-contents-block/products-table-frontend.js 5.26 kB
build/cart-blocks/cart-line-items-frontend.js 1.07 kB
build/cart-blocks/cart-order-summary-frontend.js 1.1 kB
build/cart-blocks/cart-totals-frontend.js 321 B
build/cart-blocks/empty-cart-frontend.js 345 B
build/cart-blocks/filled-cart-frontend.js 783 B
build/cart-blocks/order-summary-coupon-form-frontend.js 2.73 kB
build/cart-blocks/order-summary-discount-frontend.js 2.16 kB
build/cart-blocks/order-summary-fee-frontend.js 274 B
build/cart-blocks/order-summary-heading-frontend.js 454 B
build/cart-blocks/order-summary-shipping--checkout-blocks/order-summary-shipping-frontend.js 6.73 kB
build/cart-blocks/order-summary-shipping-frontend.js 428 B
build/cart-blocks/order-summary-subtotal-frontend.js 274 B
build/cart-blocks/order-summary-taxes-frontend.js 433 B
build/cart-blocks/proceed-to-checkout-frontend.js 1.19 kB
build/cart-frontend.js 50.3 kB
build/cart.js 46.4 kB
build/checkout-blocks/actions-frontend.js 1.8 kB
build/checkout-blocks/billing-address--checkout-blocks/shipping-address-frontend.js 4.94 kB
build/checkout-blocks/billing-address-frontend.js 925 B
build/checkout-blocks/contact-information-frontend.js 2.99 kB
build/checkout-blocks/express-payment-frontend.js 1.18 kB
build/checkout-blocks/fields-frontend.js 343 B
build/checkout-blocks/order-note-frontend.js 1.13 kB
build/checkout-blocks/order-summary-cart-items-frontend.js 3.66 kB
build/checkout-blocks/order-summary-coupon-form-frontend.js 2.88 kB
build/checkout-blocks/order-summary-discount-frontend.js 2.28 kB
build/checkout-blocks/order-summary-fee-frontend.js 275 B
build/checkout-blocks/order-summary-frontend.js 1.1 kB
build/checkout-blocks/order-summary-shipping-frontend.js 602 B
build/checkout-blocks/order-summary-subtotal-frontend.js 273 B
build/checkout-blocks/order-summary-taxes-frontend.js 433 B
build/checkout-blocks/payment-frontend.js 7.89 kB
build/checkout-blocks/shipping-address-frontend.js 1.06 kB
build/checkout-blocks/shipping-methods-frontend.js 4.98 kB
build/checkout-blocks/terms-frontend.js 1.65 kB
build/checkout-blocks/totals-frontend.js 324 B
build/checkout-frontend.js 52.5 kB
build/checkout.js 40.2 kB
build/featured-category.js 13.2 kB
build/featured-product.js 13.4 kB
build/general-style-rtl.css 1.29 kB
build/general-style.css 1.29 kB
build/handpicked-products.js 7.28 kB
build/legacy-template.js 2.83 kB
build/mini-cart-component-frontend.js 16.8 kB
build/mini-cart-contents-block/empty-cart-frontend.js 366 B
build/mini-cart-contents-block/filled-cart-frontend.js 229 B
build/mini-cart-contents-block/footer-frontend.js 3.18 kB
build/mini-cart-contents-block/items-frontend.js 236 B
build/mini-cart-contents-block/products-table-frontend.js 589 B
build/mini-cart-contents-block/shopping-button-frontend.js 287 B
build/mini-cart-contents-block/title-frontend.js 366 B
build/mini-cart-contents.js 17 kB
build/mini-cart-frontend.js 1.71 kB
build/mini-cart.js 4.58 kB
build/price-filter-frontend.js 13.5 kB
build/price-filter.js 9.4 kB
build/price-format.js 1.19 kB
build/product-add-to-cart--product-button--product-category-list--product-image--product-price--product-r--a0326d00.js 225 B
build/product-add-to-cart--product-button--product-image--product-title.js 2.66 kB
build/product-add-to-cart-frontend.js 1.24 kB
build/product-add-to-cart.js 6.47 kB
build/product-best-sellers.js 7.63 kB
build/product-button--product-category-list--product-image--product-price--product-rating--product-sale-b--e17c7c01.js 433 B
build/product-button--product-image--product-rating--product-sale-badge--product-title.js 302 B
build/product-button-frontend.js 1.89 kB
build/product-button.js 1.58 kB
build/product-categories.js 2.36 kB
build/product-category-list-frontend.js 879 B
build/product-category-list.js 501 B
build/product-category.js 8.61 kB
build/product-image-frontend.js 1.91 kB
build/product-image.js 1.61 kB
build/product-new.js 7.62 kB
build/product-on-sale.js 7.94 kB
build/product-price-frontend.js 1.91 kB
build/product-price.js 1.53 kB
build/product-query.js 648 B
build/product-rating-frontend.js 1.18 kB
build/product-rating.js 772 B
build/product-sale-badge-frontend.js 1.14 kB
build/product-sale-badge.js 817 B
build/product-search.js 2.61 kB
build/product-sku-frontend.js 380 B
build/product-sku.js 379 B
build/product-stock-indicator-frontend.js 995 B
build/product-stock-indicator.js 623 B
build/product-summary-frontend.js 1.29 kB
build/product-summary.js 920 B
build/product-tag-list-frontend.js 875 B
build/product-tag-list.js 497 B
build/product-tag.js 7.99 kB
build/product-title-frontend.js 1.33 kB
build/product-title.js 939 B
build/product-top-rated.js 7.86 kB
build/products-by-attribute.js 8.52 kB
build/rating-filter-frontend.js 6.73 kB
build/rating-filter.js 5.53 kB
build/reviews-by-category.js 11.2 kB
build/reviews-by-product.js 12.3 kB
build/reviews-frontend.js 7.01 kB
build/single-product-frontend.js 29.3 kB
build/single-product.js 10 kB
build/stock-filter-frontend.js 7.64 kB
build/stock-filter.js 7.6 kB
build/vendors--cart-blocks/cart-cross-sells-products--cart-blocks/cart-line-items--cart-blocks/cart-order--04fe80d1-frontend.js 5.26 kB
build/vendors--cart-blocks/cart-cross-sells-products--cart-blocks/order-summary-shipping--checkout-blocks--18f9376a-frontend.js 19.1 kB
build/vendors--cart-blocks/cart-cross-sells-products--product-add-to-cart-frontend.js 7.54 kB
build/vendors--cart-blocks/cart-line-items--checkout-blocks/order-summary-cart-items--mini-cart-contents---233ab542-frontend.js 3.14 kB
build/vendors--cart-blocks/order-summary-shipping--checkout-blocks/billing-address--checkout-blocks/order--5b8feb0b-frontend.js 4.85 kB
build/vendors--mini-cart-contents-block/footer-frontend.js 6.86 kB
build/wc-blocks-data.js 15.9 kB
build/wc-blocks-editor-style-rtl.css 5.24 kB
build/wc-blocks-editor-style.css 5.24 kB
build/wc-blocks-google-analytics.js 1.56 kB
build/wc-blocks-middleware.js 932 B
build/wc-blocks-registry.js 2.79 kB
build/wc-blocks-shared-context.js 1.52 kB
build/wc-blocks-shared-hocs.js 1.72 kB
build/wc-blocks-style-rtl.css 24.1 kB
build/wc-blocks-style.css 24 kB
build/wc-blocks-vendors-style-rtl.css 1.95 kB
build/wc-blocks-vendors-style.css 1.95 kB
build/wc-blocks-vendors.js 62 kB
build/wc-blocks.js 2.62 kB
build/wc-payment-method-bacs.js 816 B
build/wc-payment-method-cheque.js 811 B
build/wc-payment-method-cod.js 909 B
build/wc-payment-method-paypal.js 837 B
build/wc-settings.js 2.6 kB

compressed-size-action

@mikejolley mikejolley force-pushed the experiment/store-api-cart-jwt branch from da37d8d to 5e5349c Compare March 9, 2022 13:27
@scottopolis
Copy link

This is great Mike. In a mobile app, you'd want the Cart-Token to stay valid for longer than 48 hours, could there be a filter on that?

If not, you'd need a way to save the cart locally in the app and restore it via API.

@pluginslab
Copy link

Indeed, this is wonderful stuff. I'd say @scottopolis we'd save the cart, nonetheless. On communication failures or while on the run, going in and out of service, we'd like the cart to be updated locally, and then sync?

# Conflicts:
#	docs/internal-developers/testing/releases/440.md
#	package-lock.json
#	src/StoreApi/Routes/V1/AbstractCartRoute.php
#	src/StoreApi/Routes/V1/Batch.php
#	src/StoreApi/docs/cart.md
#	src/StoreApi/docs/checkout.md
#	src/StoreApi/docs/nonce-tokens.md
@wavvves wavvves requested a review from senadir June 16, 2022 11:54
@wavvves wavvves marked this pull request as ready for review June 16, 2022 11:55
@rubikuserbot rubikuserbot requested a review from a team June 16, 2022 11:55
@alexflorisca alexflorisca removed the request for review from tarhi-saad June 28, 2022 15:35
@senadir
Copy link
Member

senadir commented Sep 30, 2022

I just see a warning thats the branch is out of date. I'm not sure if this has changed since my afk, but I think it's preferable to rebase with trunk rather than merge in — maybe thats why? @senadir

It could be that, we enabled this check recently but I'm not sure why it's not rebasing.

@senadir
Copy link
Member

senadir commented Sep 30, 2022

So I reviewed this and while it works fine and all, it seems to return a new Cart Token on each GET/POST request, I was under the impression that the token would be somehow stable? I will investigate more if it's a sign of time or we're actually creating a new cart each time.

@senadir
Copy link
Member

senadir commented Sep 30, 2022

Okay so I checked the session table and we still have a single session even if the token is changing, this should be fine, I honestly don't understand why, because old tokens still work fine :/

Copy link
Member

@senadir senadir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, :shipit:

@wavvves
Copy link
Contributor

wavvves commented Oct 3, 2022

Okay so I checked the session table and we still have a single session even if the token is changing, this should be fine, I honestly don't understand why, because old tokens still work fine :/

@senadir this is expected behavior, all of those previous tokens point to the same session. The tokens change because with each request the expiration gets renewed, so the token hash is different. Still, all cart changes are made to the same session. We don't need to save tokens, as, being JWT, the signature is verified always.

@wavvves wavvves merged commit 6a6ef03 into trunk Oct 3, 2022
@wavvves wavvves deleted the experiment/store-api-cart-jwt branch October 3, 2022 10:49
@asumaran
Copy link

asumaran commented Oct 3, 2022

@mikejolley @wavvves With these changes, would it be possible to create and manage multiple WooCommerce Carts passing a token ID only? Context: p91TBi-5FD-p2#comment-5690

@wavvves
Copy link
Contributor

wavvves commented Oct 3, 2022

@asumaran It is possible to manage multiple carts, from different users. The Cart-Token, upon signature verification, loads up the corresponding session for it. The nonce must still be supplied for now though.

@asumaran
Copy link

asumaran commented Oct 3, 2022

It is possible to manage multiple carts, from different users.

@wavvves For my use case we need to be able to manage multiple WC Carts wether the user is logged in or not. Do you think this can be possible with these changes or adding additional code on top of it?

@wavvves
Copy link
Contributor

wavvves commented Oct 4, 2022

Only the current Cart for each session can be accessed, but as long as Cart-Token header is provided along a valid nonce for that particular session, you can manipulate multiple Carts.

An example, access GET /wc/store/v1/cart on your browser (logged on or not) and get the Cart-Token and Nonce values from the response headers. Now, with a separate rest client (make sure cookies are off), update or add items supplying those two values in the request headers. If you go back into your browser and refresh the cart endpoint, you should see all changes you made via the rest client.

@wavvves wavvves added the type: enhancement The issue is a request for an enhancement. label Oct 10, 2022
senadir added a commit to senadir/woocommerce-blocks that referenced this pull request Nov 12, 2022
* Re-apply token support

* Updated nonce headers

* Updated package-lock.json

* test commit to debug failing git hooks

* Revert "test commit to debug failing git hooks"

This reverts commit e64086b.

* JsonWebToken utility class for generating and validating HS256 JWT tokens. Removed third-party JWT library.

* Add ext-hash to composer (required by hash_hmac())

* Removed unnecessary method param.

* Tests for retrieving cart contents via Cart-Token

* Removed token tests ( we can't properly test cart token functionality until we refactor the way it intercepts calls to replace the session object )

* Abstracted payload from JsonWebToken class. We can now use it to encode custom payloads and reuse them wherever we want.

* Fixed missing check for token expiration in the payload.

* MD lint error and config fix

* Update composer.lock

* Fixed bug using the wrong nonce header.

* Refactor to properly save session data based on cart token.

* Refactored DB queries to properly use prepared statement

* Removed underscore prefix for class attributes

* Fixed spaces instead of tabs indenting composer.json. Cleaned up .editorconfig

* Cleaned up borked .md comments.

* Comment for WP_SETUP_CONFIG check.

* Reverted SQL prepared statement for including table names.

* Used hash_equals() for signature comparison. Renamed some wrongly named properties.

* Updated composer.lock

* Reverted some accidentally removed lines on some documentation files.

* Reverted accidentally removed line on docs/internal-developers/testing/releases/404.md

* Changed param type from mixed to

Co-authored-by: Paulo Arromba <17236129+wavvves@users.noreply.github.com>
Co-authored-by: Seghir Nadir <nadir.seghir@gmail.com>
@anthonymf
Copy link

Well done @senadir @mikejolley @wavvves this works well in woocommerce 7.2.2 🥳

curl -v \
--header "Nonce: <nonce goes here>" \
--header "cart-token: <cart token goes here>" \
--request POST \
"https://<your store here>/wp-json/wc/store/v1/cart/add-item?id=<id goes here>&quantity=<quantity goes here>"

with full API docs here https://github.com/woocommerce/woocommerce-blocks/tree/trunk/src/StoreApi

Endless headless possibilities!

@pierre-dekode
Copy link

This is great, thank you! Is there any official documentation mentioning this Cart-Token header somewhere?

@wavvves
Copy link
Contributor

wavvves commented Jan 20, 2023

Hi @pierre-dekode, no official documentation has been released yet about Cart-Token. Still here is some information to help you get started:

How to obtain a Cart-Token

Cart endpoints will now return a Cart-Token header in the response headers. This contains a JSON Web Token (JWT), which can later be sent as a request header to the Store API Cart and Checkout endpoints.

The quickest way to obtain one is to request GET /wp-json/wc/store/v1/cart and observe the response headers. You should see the Cart-Token header there.

image

How to use a Cart-Token

Include it in your request for GET /wp-json/wc/store/v1/cart, and the response will contain the current cart state from the session associated with the Cart-Token.
image

Tip: Add things to a cart in the browser on a logged-in standard session. Take note of the Cart-Token value returned in the cart endpoints response. Supply that token as a header in a request made through a Rest client app or Curl to receive the cart contents from your previous browser session.

Furthermore, you can manipulate cart contents (eg: POST /wp-json/wc/store/v1/cart/add-item) by submitting a valid Nonce request header along Cart-Token.

The same method will allow you to checkout using JWT and Nonce via /wp-json/wc/store/v1/checkout

@pierre-dekode
Copy link

Thank you @wavvves. That's what I ended up doing, but your message will surely help many other people :)

@jayostwalapporio
Copy link

jayostwalapporio commented May 17, 2023

@wavvves I am using the same method of 'cart-token' for cart apis but its giving error.
image

I have checked the logs and it gives following error

PHP Fatal error: Uncaught Error: Call to undefined method Automattic\WooCommerce\StoreApi\SessionHandler::has_session() in /wordpress/plugins/woocommerce-gift-cards/1.15.5/includes/class-wc-gc-cart.php:194
Stack trace:
#0 /wordpress/core/6.2.1/wp-includes/class-wp-hook.php(308): WC_GC_Cart->after_calculate_totals(Object(WC_Cart))
#12058 /wordpress/core/6.2.1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters(NULL, Array)
#2 /wordpress/core/6.2.1/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#3 /wordpress/plugins/woocommerce/7.7.0/includes/class-wc-cart.php(1403): do_action('woocommerce_aft...', Object(WC_Cart))
#4 /wordpress/plugins/woocommerce/7.7.0/packages/woocommerce-blocks/src/StoreApi/Utilities/CartController.php(43): WC_Cart->calculate_totals()
#5 /wordpress/plugins/woocommerce/7.7.0/packages/woocommerce-blocks/src/StoreApi/Routes/V1/AbstractCartRoute.php(98): Automattic\WooCommerce\StoreApi\Utilities\CartController->calculate_totals()
#6 /wordpress/core/6.2.1/wp-includes/rest-api/class-wp-rest-server.php(1181): Automattic\WooCommerce\StoreApi\Routes\V1\AbstractCartRoute->get_response(Object(WP_REST_Request))
#7 /wordpress/core/6.2.1/wp-includes/rest-api/class-wp-rest-server.php(1028): WP_REST_Server->respond_to_request(Object(WP_REST_Request), '/wc/store/v1/ca...', Array, NULL)
#8 /wordpress/core/6.2.1/wp-includes/rest-api/class-wp-rest-server.php(442): WP_REST_Server->dispatch(Object(WP_REST_Request))
#9 /wordpress/core/6.2.1/wp-includes/rest-api.php(410): WP_REST_Server->serve_request('/wc/store/v1/ca...')
#10 /wordpress/core/6.2.1/wp-includes/class-wp-hook.php(308): rest_api_loaded(Object(WP))
#11 /wordpress/core/6.2.1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters(NULL, Array)
#12 /wordpress/core/6.2.1/wp-includes/plugin.php(565): WP_Hook->do_action(Array)
#13 /wordpress/core/6.2.1/wp-includes/class-wp.php(399): do_action_ref_array('parse_request', Array)
#14 /wordpress/core/6.2.1/wp-includes/class-wp.php(780): WP->parse_request('')
#15 /wordpress/core/6.2.1/wp-includes/functions.php(1334): WP->main('')
#16 /wordpress/core/6.2.1/wp-blog-header.php(16): wp()
#17 /wordpress/core/6.2.1/index.php(17): require('/wordpress/core...')
#18 {main}
thrown in /wordpress/plugins/woocommerce-gift-cards/1.15.5/includes/class-wc-gc-cart.php on line 194

Please help me I am really stuck in this and I am new to woocommerce

@wavvves
Copy link
Contributor

wavvves commented Jun 28, 2023

@jayostwalapporio thanks for reporting that. There is currently work in progress trying to fix that problem. Please create an issue for that so I can link it to the PR

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
focus: rest api Work impacting REST api routes. type: enhancement The issue is a request for an enhancement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Load cart via key for headless and mobile apps