CORS error when calling App Proxy from Shopify customer account UI extension

CORS error when calling App Proxy from Shopify customer account UI extension

rvj1
Shopify Partner
3 0 0

 

I'm building a Shopify app that uses a Customer Account UI Extension (customer-account.order.action.render) and fetches data from a backend App Proxy route (/apps/proxytest).


Expected Behavior

When I open this proxy URL directly in the browser:

 

https://my-socks-store.myshopify.com/apps/proxytest

 

I get the expected JSON response:

 
{ "message": "Proxy GET successful!" }


 Actual Problem in Extension

When I try calling this endpoint inside my extension using the full URL (In Shopify), I get a CORS error in the browser console:

 

Access to fetch at 'https://my-socks-store.myshopify.com/apps/proxytest' 
from origin 'https://extensions.shopifycdn.com' has been blocked by CORS policy: 
Response to preflight request doesn't pass access control check: 
Redirect is not allowed for a preflight request

C:\new-app\extensions\ui-extension\src\Modal.tsx

import {
  reactExtension,
  Modal,
  CustomerAccountAction,
  Text,
  Button,
  BlockStack,
  useApi,
} from '@shopify/ui-extensions-react/customer-account';
import { useState, useEffect } from 'react';

reactExtension('customer-account.order.action.render', () => <ExchangeModal />);

function ExchangeModal() {
  const [exchangeData, setExchangeData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const api = useApi();

  useEffect(() => {
    const fetchExchangeData = async () => {
      try {
        const response = await fetch('https://my-socks-store.myshopify.com/apps/proxytest', {
          credentials: 'include',
          headers: {
            Accept: 'application/json',
            'Content-Type': 'application/json',
          },
        });
        if (!response.ok) {
          throw new Error('Failed to fetch exchange data');
        }
        const data = await response.json();
        setExchangeData(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchExchangeData();
  }, []);

  if (loading) {
    return (
      <CustomerAccountAction title="Exchange Item">
        <BlockStack>
          <Text>Loading exchange data...</Text>
        </BlockStack>
      </CustomerAccountAction>
    );
  }

  if (error) {
    return (
      <CustomerAccountAction title="Exchange Item">
        <BlockStack>
          <Text>Error: {error}</Text>
          <Button
            onPress={() => {
              setError(null);
              setLoading(true);
            }}
          >
            Retry
          </Button>
        </BlockStack>
      </CustomerAccountAction>
    );
  }

  return (
    <CustomerAccountAction title="Exchange Item">
      <BlockStack>
        {exchangeData && exchangeData.length > 0 ? (
          <>
            <Text>Exchange History:</Text>
            {exchangeData.map((exchange) => (
              <BlockStack key={exchange.id}>
                {exchange.exchangeChain.map((chain, index) => (
                  <BlockStack key={index}>
                    <Text>Product: {chain.productName}</Text>
                    <Text>Type: {chain.type}</Text>
                    <Text>Date: {new Date(chain.date).toLocaleDateString()}</Text>
                  </BlockStack>
                ))}
              </BlockStack>
            ))}
          </>
        ) : (
          <Text>No exchange history found.</Text>
        )}
        <Button onPress={() => { /* handle exchange logic */ }}>Start New Exchange</Button>
      </BlockStack>
    </CustomerAccountAction>
  );
}

 

C:\new-app\app\routes\app.proxy.tsx

import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { authenticate } from "app/shopify.server";

export async function loader({ request }: LoaderFunctionArgs) {
  // Handle CORS preflight request first
  if (request.method === "OPTIONS") {
    return new Response(null, {
      status: 204, // No Content is standard for preflight
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Credentials": "true",
        "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
        "Access-Control-Allow-Headers": "Content-Type, Authorization, Accept",
        "Access-Control-Max-Age": "86400", // Cache preflight for 24 hours
      },
    });
  }

  // Authenticate only for non-OPTIONS requests
  const { session } = await authenticate.public.appProxy(request);
  if (!session) {
    return json(
      { error: "No session" },
      {
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Credentials": "true",
        },
      }
    );
  }

  // Return response for GET requests
  return json(
    { message: "Proxy GET successfull!" },
    {
      headers: {
        "Access-Control-Allow-Origin": "https://extensions.shopifycdn.com",
        "Access-Control-Allow-Credentials": "true",
      },
    }
  );
}

 

when using partial url (/apps/proxytest) got this error

Exchange Item
Error: Failed to construct 'Request': Failed to parse URL from /apps/proxytest

 

Subpath prefix: apps
Subpath: proxytest
Proxy URL: https://cs-dakota-stock.trycloudflare.com/app/proxy


Replies 2 (2)

trenkwill1
Shopify Partner
3 1 0

Did you get it to work? I'm facing the same issue

rvj1
Shopify Partner
3 0 0

No, still on the same issue!!