Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot fetch app proxy URL from customer account UI extension #1646

Closed
BaggioGiacomo opened this issue Jan 10, 2024 · 10 comments
Closed

Cannot fetch app proxy URL from customer account UI extension #1646

BaggioGiacomo opened this issue Jan 10, 2024 · 10 comments
Labels

Comments

@BaggioGiacomo
Copy link

BaggioGiacomo commented Jan 10, 2024

Please list the package(s) involved in the issue, and include the version you are using

  • @shopify/ui-extensions-react": "0.0.0-unstable-20231024205236

Describe the bug

I have a customer account ui extension with customer-account.profile.block.render as target.
I want to add a card on the customer profile page to let him see and update its own metafields.
Since the GraphQL Customer Account API doesn't let me update the customer metafields, I have to call my own server (in this server I have my shopify app)

The problem is that I get a CORS policy error:

Access to fetch at 'https://admin.shopify.com/store/<store_name>/apps/<app_name>/app_proxy/metafields/bulk_update' from origin 'https://cdn.shopify.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Is this the correct way to do what I want to do?
Shall I call directly my own server without app proxy? If yes, how can I correctly verify that the request is authenticated? I'm passing a bearer token (generated using the useSessionToken() hook) on the request

Steps to reproduce the behavior:

  1. Generate a customer account ui extension
  2. Set customer-account.profile.block.render as target
  3. Try to call your own server using app proxy

Expected behavior

The app proxy should correctly works instead of getting a CORS policy error
https://shopify.dev/docs/api/customer-account-ui-extensions/unstable/configuration#network-access

Additional context

Why does this mutation allow to update only the customer first name and last name?

@BaggioGiacomo BaggioGiacomo added the bug Something isn't working label Jan 10, 2024
@robin-drexler
Copy link
Member

Hey,

you can generally make requests to App Proxied URLs.

We have documentation on restrictions and required CORS headers here: https://shopify.dev/docs/api/customer-account-ui-extensions/unstable/configuration#network-access:~:text=Control%2DAllow%2DOrigin%3A%20*-,App%20Proxy,-UI%20extensions%20can

When you make a request to your own backend, be it through app proxy or not, make sure to send the session token (as you mentioned) and validate it in your backend.

https://shopify.dev/docs/api/customer-account-ui-extensions/unstable/apis/session-token#examples
https://shopify.dev/docs/api/customer-account-ui-extensions/unstable/configuration#network-access:~:text=Security%20considerations

@BaggioGiacomo
Copy link
Author

BaggioGiacomo commented Jan 18, 2024

Hey there, thanks for your response!

The problem with the session token is that it just verifies the integrity of the data, not that the request comes from Shopify. This is why I want to use app proxy.

For network calls to succeed, your server must support cross-origin resource sharing (CORS) for null origins by including this response header

If I call the app proxy, shouldn't the https://admin.shopify.com adds the Access-Control-Allow-Origin: * header to the response? My server doesn't receive any request.

Temporarily I'm calling my server directly (passing the session token and verifying it), but I prefer passing through the app proxy in the future :)

@robin-drexler
Copy link
Member

it just verifies the integrity of the data, not that the request comes from Shopify

The token can only be created by Shopify and not by a third party since it's signed using the app's shared secret. It also contains a unique identifier so you can ensure each token is only used once.

My server doesn't receive any request.

Does your server receive a request if you take the browser and hence CORS out of the equation and send a request to your server via the app proxy by using e.g. curl or any other tool to send http requests?

@BaggioGiacomo
Copy link
Author

Does your server receive a request if you take the browser and hence CORS out of the equation and send a request to your server via the app proxy by using e.g. curl or any other tool to send http requests?

Yes, I already use it on my theme app extension.
I also receive the request if i make it using insomnia or curl 👍🏻

@robin-drexler
Copy link
Member

If I call the app proxy, shouldn't the https://admin.shopify.com adds the Access-Control-Allow-Origin: * header to the response?

No, you'd have to do this. Does your backend handle OPTIONS requests as well? This is how CORS preflight requests are being sent.

@BaggioGiacomo
Copy link
Author

If I make the request to the app proxy, my server doesn't receive anything (empty logs).
The app proxy should send the request to a basic Rails controller, but It doesn't receive anything.
I give you the code for more context:

# frozen_string_literal: true

class AppProxyController < ApplicationController
  before_action :verify_shopify_request
  around_action :activate_shopify_session

  private

  def verify_shopify_request
    query_string = request.query_string
    query_hash = Rack::Utils.parse_query(query_string)
    signature = query_hash.delete("signature")
    sorted_params =
      query_hash.collect { |k, v| "#{k}=#{Array(v).join(",")}" }.sort.join
    calculated_signature =
      OpenSSL::HMAC.hexdigest(
        OpenSSL::Digest.new("sha256"),
        ENV["SHOPIFY_API_SECRET"],
        sorted_params,
      )
    is_verified =
      ActiveSupport::SecurityUtils.secure_compare(
        signature,
        calculated_signature,
      )
    unless is_verified
      render(json: { message: "Unauthorized request" }, status: :unauthorized)
    end
  end

  def activate_shopify_session(&block)
    shop_domain = params[:shop]
    Current.shop = Shop.find_by(shopify_domain: shop_domain)
    ShopifyAPI::Auth::Session.new(
      shop: shop_domain,
      access_token: Current.shop.shopify_token,
    )
    Current.shop.with_shopify_session(&block)
  end
end

@robin-drexler
Copy link
Member

Can you share the URL you're requesting from the extension, please? Feel free to remove the shop name from it.

@BaggioGiacomo
Copy link
Author

https://iop-lab-6.myshopify.com/apps/amplius-giacomo/app_proxy/metafields/bulk_update
image
image

I'm sure the app proxy is correctly configured since I already use it on my theme app extension

@robin-drexler
Copy link
Member

Hey, thanks for the additional context.

I had a another look and I'm afraid you won't be able to use the App Proxy during the dev preview.
As noted in the documentation, App Proxy requests don't work in stores that are password protected.

Development stores are always password protected and currently you can run customer account extensions (that are part of the developer preview) only inside development stores.

For now you'd have send requests to your backend directly. You can provide the shop name via a query parameter for example and validate it against the shop that's part of the session token JWT.

@BaggioGiacomo
Copy link
Author

Oh ok, thanks for the clarification, it makes sense 👍🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants