woocommerce-blocks
woocommerce-blocks copied to clipboard
Store API - Cart JWT tokens/session handling
POC for #5683 Includes changes from https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/6020
Returns a header containing a Cart-Token
valid for 48h which can be added as a request header to load that cart by customer ID.
Works by extending the WC_Session class to intercept requests with a valid header.
You can test this by:
- Using a REST API client
- Disabling basic auth
- Adding something to the cart via POST
/wc/store/v1/cart/add-item
. Note down the value of theCart-Token
header. - Deleting or disabling cookies before requesting GET
/wc/store/v1/cart
- No items
- Now repeat this request with a header called
Cart-Token
and the value you noted earlier. - Cart response has items!
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 |
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.
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?
The release ZIP for this PR is accessible via:
https://wcblocks.wpcomstaging.com/wp-content/uploads/woocommerce-gutenberg-products-block-5953.zip
The release ZIP for this PR is accessible via:
https://wcblocks.wpcomstaging.com/wp-content/uploads/woocommerce-gutenberg-products-block-5953.zip
The release ZIP for this PR is accessible via:
https://wcblocks.wpcomstaging.com/wp-content/uploads/woocommerce-gutenberg-products-block-5953.zip
This is looking good! Just one question, would it be possible to add a test that does basically what the PR testing instructions say? (adding a product, getting the cart-token and retrieve the cart using it).
This is looking great. I have a few questions if you don't mind, as I understand we won't rely on the session cookie anymore.
Let's say I add an item, and get the Cart-Token
, what happens if I then add another item? Do I need to use the same Cart-Token
on that add-item request?
Also, what if we empty the cart, is the Cart-Token
the same or will the add-item provide a new one?
Thanks! đđģ
Let's say I add an item, and get the Cart-Token, what happens if I then add another item? Do I need to use the same Cart-Token on that add-item request?
Yes, you will need to send your current token, and you will receive a new token. This is because tokens reflect an actual cart content, so changing its content will generate a new token.
You will need to manage the token the same way you manage a nonce, by intercepting incoming and outgoing requests.
Also, what if we empty the cart, is the Cart-Token the same or will the add-item provide a new one?
An empty cart will have no token, adding an item will get you a new token, this token might be the same if you added the same item again, because tokens are a hash of the cart content.
This is looking good! Just one question, would it be possible to add a test that does basically what the PR testing instructions say? (adding a product, getting the cart-token and retrieve the cart using it).
Hey @albarin, thanks for reviewing this. Actually, I tried to add those tests without success, testing headers is not very easy, specially when we use filters to replace session handling etc.
Writing a test using Javascript would possibly be a way to have more control over headers and cookies, since it runs on the "client side"?
Writing a test using Javascript would possibly be a way to have more control over headers and cookies, since it runs on the "client-side"?
Initially, I started out with E2E for that with @opr's help, but it was turning out not to be as straightforward as expected and I went back into PHP unit testing as I had previous success with it.
I'll give JS another go đ
EDIT: Due to including this in the next 8.5.0 release next week, the test will be added in a separate PR.
Thanks for working on this some more. I made 2 small comments.
Does this PR need to be rebased with trunk? Github is complaining about conflicts. I'm happy to do some more in depth testing after that to ensure there are no regressions.
Where is the conflict warning đ¤? I have been updating it with trunk changes, and so far I haven't dealt with any conflicts.
Can I mark the other comments as resolved?
Can I mark the other comments as resolved?
đđģ Yup
Where is the conflict warning
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
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.
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.
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 :/
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.
@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
@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.
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?
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.
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!
This is great, thank you! Is there any official documentation mentioning this Cart-Token
header somewhere?
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.
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.
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
Thank you @wavvves. That's what I ended up doing, but your message will surely help many other people :)
@wavvves I am using the same method of 'cart-token' for cart apis but its giving error.
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)) woocommerce/woocommerce-blocks#12058 /wordpress/core/6.2.1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters(NULL, Array) woocommerce/woocommerce-blocks#2 /wordpress/core/6.2.1/wp-includes/plugin.php(517): WP_Hook->do_action(Array) woocommerce/woocommerce-blocks#3 /wordpress/plugins/woocommerce/7.7.0/includes/class-wc-cart.php(1403): do_action('woocommerce_aft...', Object(WC_Cart)) woocommerce/woocommerce-blocks#4 /wordpress/plugins/woocommerce/7.7.0/packages/woocommerce-blocks/src/StoreApi/Utilities/CartController.php(43): WC_Cart->calculate_totals() woocommerce/woocommerce-blocks#5 /wordpress/plugins/woocommerce/7.7.0/packages/woocommerce-blocks/src/StoreApi/Routes/V1/AbstractCartRoute.php(98): Automattic\WooCommerce\StoreApi\Utilities\CartController->calculate_totals() woocommerce/woocommerce-blocks#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)) woocommerce/woocommerce-blocks#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) woocommerce/woocommerce-blocks#8 /wordpress/core/6.2.1/wp-includes/rest-api/class-wp-rest-server.php(442): WP_REST_Server->dispatch(Object(WP_REST_Request)) woocommerce/woocommerce-blocks#9 /wordpress/core/6.2.1/wp-includes/rest-api.php(410): WP_REST_Server->serve_request('/wc/store/v1/ca...') woocommerce/woocommerce-blocks#10 /wordpress/core/6.2.1/wp-includes/class-wp-hook.php(308): rest_api_loaded(Object(WP)) woocommerce/woocommerce-blocks#11 /wordpress/core/6.2.1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters(NULL, Array) woocommerce/woocommerce-blocks#12 /wordpress/core/6.2.1/wp-includes/plugin.php(565): WP_Hook->do_action(Array) woocommerce/woocommerce-blocks#13 /wordpress/core/6.2.1/wp-includes/class-wp.php(399): do_action_ref_array('parse_request', Array) woocommerce/woocommerce-blocks#14 /wordpress/core/6.2.1/wp-includes/class-wp.php(780): WP->parse_request('') woocommerce/woocommerce-blocks#15 /wordpress/core/6.2.1/wp-includes/functions.php(1334): WP->main('') woocommerce/woocommerce-blocks#16 /wordpress/core/6.2.1/wp-blog-header.php(16): wp() woocommerce/woocommerce-blocks#17 /wordpress/core/6.2.1/index.php(17): require('/wordpress/core...') woocommerce/woocommerce-blocks#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
@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