woocommerce-blocks icon indicating copy to clipboard operation
woocommerce-blocks copied to clipboard

Store API - Cart JWT tokens/session handling

Open mikejolley opened this issue 2 years ago â€ĸ 12 comments

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 the Cart-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!

mikejolley avatar Feb 25 '22 17:02 mikejolley

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

github-actions[bot] avatar Feb 25 '22 17:02 github-actions[bot]

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.

scottopolis avatar May 13 '22 14:05 scottopolis

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?

pluginslab avatar May 13 '22 14:05 pluginslab

The release ZIP for this PR is accessible via:

https://wcblocks.wpcomstaging.com/wp-content/uploads/woocommerce-gutenberg-products-block-5953.zip

github-actions[bot] avatar Jul 26 '22 23:07 github-actions[bot]

The release ZIP for this PR is accessible via:

https://wcblocks.wpcomstaging.com/wp-content/uploads/woocommerce-gutenberg-products-block-5953.zip

github-actions[bot] avatar Jul 26 '22 23:07 github-actions[bot]

The release ZIP for this PR is accessible via:

https://wcblocks.wpcomstaging.com/wp-content/uploads/woocommerce-gutenberg-products-block-5953.zip

github-actions[bot] avatar Jul 27 '22 09:07 github-actions[bot]

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).

albarin avatar Aug 25 '22 08:08 albarin

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! 🙏đŸģ

pluginslab avatar Aug 25 '22 11:08 pluginslab

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.

senadir avatar Aug 25 '22 12:08 senadir

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.

wavvves avatar Aug 26 '22 09:08 wavvves

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"?

schmitzoide avatar Aug 26 '22 09:08 schmitzoide

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.

wavvves avatar Sep 07 '22 10:09 wavvves

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?

wavvves avatar Sep 28 '22 16:09 wavvves

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

mikejolley avatar Sep 29 '22 09:09 mikejolley

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 avatar Sep 30 '22 10:09 senadir

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 avatar Sep 30 '22 12:09 senadir

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 avatar Sep 30 '22 12:09 senadir

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 avatar Oct 03 '22 10:10 wavvves

@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 avatar Oct 03 '22 16:10 asumaran

@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.

wavvves avatar Oct 03 '22 17:10 wavvves

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?

asumaran avatar Oct 03 '22 17:10 asumaran

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 avatar Oct 04 '22 16:10 wavvves

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!

anthonymf avatar Dec 29 '22 20:12 anthonymf

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

pierre-dekode avatar Jan 19 '23 13:01 pierre-dekode

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

wavvves avatar Jan 20 '23 13:01 wavvves

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

pierre-dekode avatar Jan 20 '23 13:01 pierre-dekode

@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)) 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 avatar May 17 '23 11:05 jayostwalapporio

@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

wavvves avatar Jun 28 '23 12:06 wavvves