Our Partner & Developer boards on the community are moving to a brand new home: the .dev community forums! While you can still access past discussions here, for all your future app and storefront building questions, head over to the new forums.

/auth/login 404 with shopify app proxy

/auth/login 404 with shopify app proxy

CarlosC24
Shopify Partner
8 0 2

I followed a YT video on how to set up an app proxy. I also looked at a lot of Shopify forum posts to no avail
I get this error in the dev tools console (I think the Youtuber deleted my comment when I tried asking about this error)
GET https://testingwishlist.myshopify.com/auth/login 404 (Not Found)

and this from my terminal
GET /app/proxy/?shop=testingwishlist.myshopify.com&logged_in_customer_id=&path_prefix=%2Fapps%2Fproxytest&timestamp=1718050314&signature=819a839
ceb80bf73ef9b978ad39c7ddcf7324744ea00b4d06710ffefb5cbff4f 302

Here is my proxy.liquid and my proxy.js

<body>
    <button onclick="testProxy()">test proxy</button>
</body>

<script async src={{ "proxy.js" | asset_url }} defer></script>

{% schema %}
{
  "name": "Proxy Embed",
  "target": "section",
  "settings": [
    {
      "type": "header",
      "content": "Proxy button to customer wish list"
    }
  ]
}
{% endschema %}

 

function testProxy() {
    return new Promise((resolve, reject) => {
        fetch("https://testingwishlist.myshopify.com/apps/proxytest"), {
            method: 'POST',
            redirect: 'manual',
            headers: {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*',
            }
        }
    }).then((response) => {
        console.log(response, 'response');
    }).then((data) => {
        resolve(data)
    }).catch((error) => {
        reject(error)
    })
}

Here is my app.proxy.jsx which is in the routes folder

import { authenticate } from '../shopify.server'
import { Page } from '@shopify/polaris'
import { cors } from 'remix-utils'
import { json } from '@remix-run/node';

export const loader = async ({request}) => {
    console.log('---hit app proxy----')

    const {session} = await authenticate.public.appProxy(request)
    
    return await cors(request, json({status: 200}))
}

const Proxy = () => {
    return <Page>proxy </Page>
}

export default Proxy

The console.log in my app.proxy.jsx does turn up in the terminal along with the error, so I at least know that its connected to the fetch in proxy.js

My App proxy settings in Shopify Partner is : 
Subpath prefix: apps
Subpath: proxytest
Proxy URL: https://journalists-attribute-slim-paste.trycloudflare.com/app/proxy 
The URL changes each time I restart the app from the terminal, so I always make sure to update the Proxy URL

The App Proxy Url from the admin settings is: https://testingwishlist.myshopify.com/apps/proxytest

I did add this to my remix.config.js file but it didn't change anything 

serverDependenciesToBundle: [
    /^remix-utils.*/,
  ],



 

Replies 23 (23)

taniokas
Shopify Partner
13 0 3

I'm getting this error too! is this Di Stephano's tutorial hahah? Do you have hosting setup on yours, I'm thinking it might have to do with how my app is built up to fly.io. But I'm not sure. I'm getting a 404 error on my proxy GET, same as you

CarlosC24
Shopify Partner
8 0 2

I don't have a dedicated hosting setup, just been using application_url from the .toml file for my proxy url

taniokas
Shopify Partner
13 0 3

Maybe try adding this under the [webhooks] section in the shopify.app.toml ?

[app_proxy]
url = "yourcloudflarelink.com"
subpath = "yoursubpath"
prefix = "apps"

 

This from someone in the discord server where they actually have their proxy url only set with the main directory. Unlike the video where he does the full proxy app url.

taniokas_0-1718440797825.png

 

here's the discord discussion. https://discord.com/channels/597504637167468564/1251292194631843952

I'll be trying out and debugging more tomorrow

CarlosC24
Shopify Partner
8 0 2

I just looked and my shopify.app.toml file already has that section in there. I think it auto updates whenever I restart the server.

taniokas
Shopify Partner
13 0 3

gotcha, gotcha. Yeah so the discord steps didn't fix anything? I can try messaging the guy on discord to see if he has an example 

arifkpi
Shopify Partner
5 1 1

Hope it will help someone in future.

I'm able to successfully fetch data using App Proxy, I followed Di Stephano's tutorial initially but there was some issue. So I need to do some adjustments.

The first thing I did is use "shopify.app.toml" file to put the proxy settings because it will auto update the URL as needed. Example code I put there:

# start of code

[app_proxy]
subpath = "styledata"
prefix = "apps"
# end of code
 
Explanation
As the URL is "https://base-jewelry-dayton-hold.trycloudflare.com/app/proxy", so I need to create a file on "routes" folder name "app.proxy.jsx", so it will match with the URL on the backend. 

For the frontend, it will be like: my-store-url/{prefix}/{subpath}, so something like that: https://my-store-url.myshopify.com/apps/styledata

 

So now I can send a request by like that, I put the URL relative so it will match with store URL:

 

# start of code

<script>
function fetchStyleData(){
return new Promise((resolve, reject) => {
fetch('/apps/styledata', {
method: 'GET',
redirect: 'manual',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
}
}).then((response) => {
console.log({response})
}).then((data) => {
resolve(data);
}).catch((error) => {
reject(error);
});
});
}
</script>

<button onclick="fetchStyleData()">Request Data</button>

# end of code

And finally code for the backend (app.proxy.jsx)

 

// start of code

import db from "../db.server";
import { authenticate } from "../shopify.server";
import { json } from "@remix-run/node";

export const loader = async ({ request }) => {
const { admin, session } = await authenticate.public.appProxy(request);
const shopData = await admin.rest.resources.Shop.all({ session: session });
const shopId = shopData?.data[0].id;

const styler = await db.styler.findFirst({
where: {
storeId: shopId.toString(),
},
});

const response = json({
ok: true,
message: "Success",
data: styler,
});

return (request, response);
};

# end of code


I'm using a loader because I need to fetch the data, if you need POST/PUT/DELETE etc you need to use action then.

And finally I'm getting the data the attached screenshot.
Screenshot 2024-07-01 at 12.17.17 PM.png

 

 

taniokas
Shopify Partner
13 0 3

oh wow! Thank you so much @arifkpi for the code sample and assist! Do you happen to have the app pushed to the partner store, or are you running the app locally with 'npm run dev'?

 

I setup a new test app to follow your setup, but I'm still hitting 500 errors. This is in context of running the app locally with 'npm run dev'. It looks like this discussion was maybe relevant for the error we're still seeing, but I'm not too sure after seeing your screenshots that its working on your machine: https://community.shopify.com/c/online-store-and-theme/application-proxy-there-was-an-error-in-the-t...

 

Thanks again!

 

500 errors.png

arifkpi
Shopify Partner
5 1 1

The 500 error code indicates that there is a server error. I think your proxy code contains an error; please double-check the code because I got the same error as well when my code was faulty.

taniokas
Shopify Partner
13 0 3

I think I was able to get it, and it may have something to do with my access_scopes in shopify.app.toml file? What access scopes are you using @arifkpi ? I currently have: read_customers,read_products,write_customers,write_products

arifkpi
Shopify Partner
5 1 1

For the app I'm working on, I don't need any shop/customer data; for that reason, access_scopes is empty in my config.

AnoopJangir
Shopify Partner
5 0 1

Hey! it worked. Thank you.

aidendev
Shopify Partner
53 1 1

hey did you use shopify's implementation of AuthenticateAppProxy?

 

taniokas
Shopify Partner
13 0 3

from my snippets above and OP's we're using this method: 

authenticate.public.appProxy()

this is with the Remix app template with jsx

 

https://shopify.dev/docs/api/shopify-app-remix/v1/authenticate/public/app-proxy

aidendev
Shopify Partner
53 1 1

if you don't mind, I noticed you also are utilizing npm run dev. Im in early stages of development so im wondering how my .env would get read. is there a common method of passing the environment variables in development? ive been looking everywhere

thank you for getting back this quick btw

taniokas
Shopify Partner
13 0 3

no problem! as for envs, for local dev you basically use the shopify.app.toml and the .env file that gets created after your initial: 

 

shopify app init

 

 

I'd also say, even if you're just in dev/test stage, definitely move your app up to it's hosting service. It'll ensure that your connection to the app hosting and the basic default app is working with the env configurations. I ran into hiccups during those steps, and it would've saved us so much time just making sure things were squared away then.

 

Your App Hosting service usually has a way to config envs. I use Render.com for app hosting as they have a free development tier (if you notice your app is down, you'll have to manually spin it back up on the free tier). I also believe Render became a new partner with Shopify recently in the last couple of months. We previously used Fly.io and often had issues with spin-up, Render was seamless.

https://docs.render.com/deploy-shopify-app

 

lmk if anything else, best of luck.

aidendev
Shopify Partner
53 1 1

Believe it or not, I've been in a one month hiccup after changing from .NET to React Remix for Shopify Development. I feel like if you took a quick look at my issue you may have solved this many times by now:


Essentially, we leverage a static ngrok_domain.  

made my app proxy settings as

url: shopifystore.com/app/api 

prefix: apps

subpath: member

 

Prior to this I created app.api.jsx, and I'm currently seeing the shopify 404 page with the url as (auth/login) despite the fact I authenticated the proxy request (or so I assume..).
my app.api.jsx :

 

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


export const loader = async ({ request }) => {
  // Parse the URL to extract query parameters
  const url = new URL(request.url);

  // Get query parameters from the URL
  const shop = url.searchParams.get("shop");
  const signature = url.searchParams.get("signature");
  const timestamp = url.searchParams.get("timestamp");

  console.log("Incoming Query Parameters:");
  console.log("Shop:", shop);
  console.log("Signature:", signature);
  console.log("Timestamp:", timestamp);

  // Check if the required parameters are missing
  if (!shop || !signature) {
    console.log("Missing required parameters.");
    return json({ error: "Missing required parameters" });
  }

  try {
    // Authenticate the app proxy request
    const { admin, session} = await authenticate.public.appProxy(request);

    // Log success
    console.log("Authentication successful:", { shop, session });

    // Respond with success
    return json({
      ok: true,
      message: "App Proxy Authentication Successful",
      shop,
    });
  } catch (error) {
    // Log any errors during authentication
    console.log("Authentication failed:", error);
    return json({ error: "Authentication failed", details: error.message });
  }
};

export default function AppApi() {
  return (
    <div>
      <h1>App Proxy Route - JSON Data</h1>
      <p>This route handles the app proxy requests.</p>
    </div>
  );
}

 

I have a feeling Im overcomplicating it 😧 my remix terminal gave me the following:

15:31:20 │ remix │ Incoming Query Parameters:
15:31:20 │ remix │ Shop: non-null-value
15:31:20 │ remix │ Signature:
non-null-value
15:31:20 │ remix │ Timestamp: non-null-value
15:31:20 │ remix │ [shopify-app/INFO] Authenticating app proxy request
15:31:20 │ remix │ Authentication successful: {
15:31:20 │ remix │ shop: 'non-null-value',
15:31:20 │ remix │ session: Session {
15:31:20 │ remix │ id: 'non-null-value',
15:31:20 │ remix │ shop: 'non-null-value',
15:31:20 │ remix │ state: '',
15:31:20 │ remix │ isOnline: false,
15:31:20 │ remix │ scope: 'write_products',
15:31:20 │ remix │ expires: undefined,
15:31:20 │ remix │ accessToken: 'non-null-value',
15:31:20 │ remix │ onlineAccessInfo: undefined
15:31:20 │ remix │ }

 

edit: i also didnt see a .env file after my initial shopify app init. i just made one manually

 

taniokas
Shopify Partner
13 0 3

oh you know what, I also had issues with the loader function throwing errors when I tried to hit the shop and admin during the await authenticate.public.appProxy(request)! could you maybe delete your current loader and just try this snippet to confirm app proxy is coming through correctly: 

 

export const loader = async ({ request }) => {

  const { liquid } = await authenticate.public.appProxy(request);

  return liquid("Wow {{shop.name}} goes here");

};

 

aidendev
Shopify Partner
53 1 1

that worked!

that's so interesting.. So if I were to add buttons to this proxy page now it can still be in this app.api.jsx correct? My goal is to do simple GET/POST from my external api and have it display on my proxy page - can all that logic be done in one file? 

thanks so much for your time, truly you helped me make a breakthrough.

taniokas
Shopify Partner
13 0 3

awesome, glad it worked! There definitely must be something bugged with the {admin, shop} then. But yeah technically if you wanted to load data and FE on the one proxy page you can do it there. just make sure to have the return() JSX function with your Polaris components. For loading data, I use graphQL on the loader() function which also had some quirks, but ofc, I think you're set from there! if you run into anything I may be able to assist, all the best


aidendev
Shopify Partner
53 1 1

Well i do have a 100th question to ask 😂
if you had any reason to call an external api, aside from :

 

  • using loader for get, action for other cruds
  • passing the CORS header we all know and setting content to application/json

were there any caveats/necessities needed to ensure successfull http requests? 

taniokas
Shopify Partner
13 0 3

nah it should work as expected. Just make sure if you're using .envs to have it both on your local and add it during the setup stage to your Prod hosting. If you're making the API yourself, make sure to whitelist your local app's and shop URL, and also your App's Prod hosting URL. The data formatting might just be the only "caveat". 

 

You'll basically use the loader in conjunction with the useLoaderData() function. And then you can just map the data returned in any of the Polaris components as needed: 

function Component() {
 const data = useLoaderData();
}

 but lmk once you get to that step if there's an specific hiccup you run into.

 

dw about asking questions, because I'm certain someone else in the future will have it too! These forums are the only way I've been successful in completing a Shopify app hahah, the official docs are kinda an abyss. 

aidendev
Shopify Partner
53 1 1

So I managed to make a button by dynamically adding js with liquid (for some reason liquid seems to be the only thing my page likes..)

 

import { authenticate } from "../shopify.server"; // Assuming authenticate method is defined here

export const loader = async ({ request }) => {
  const { liquid } = await authenticate.public.appProxy(request);

  // Return Liquid template with a button and a placeholder for the token
  const liquidTemplate = `
    <html>
    <body>
      <h1>Wow {{shop.name}} goes here</h1>
      <button id="fetchToken">Fetch Token</button>
      <p id="tokenOutput"></p>
      <script>
        document.getElementById('fetchToken').addEventListener('click', async function() {
          const response = await fetch('/api/authorize');
          const data = await response.json();
          
          if (data.token) {
            document.getElementById('tokenOutput').innerText = "Bearer Token: " + data.token;
          } else {
            document.getElementById('tokenOutput').innerText = "Failed to retrieve token.";
          }
        });
      </script>
    </body>
    </html>
  `;

  return liquid(liquidTemplate);
};

 

this resulted in a 404 for some reason, even though i named my jsx file app.api.authorize.jsx:

aidendev_0-1724793385947.png

this is the route im planning to call with the button, that is responsible for making the actual call to my external api:

import { json } from "@remix-run/node"; // For handling loader data
import { useLoaderData } from "@remix-run/react"; // For accessing data in the component
import Polaris from "@shopify/polaris"; // Import Polaris components
const { Page, Layout, Card, Heading } = Polaris;

export const loader = async () => {
  // Hardcoded API URL and headers for token retrieval
  const apiUrl = "hidden"; // Replace with actual API URL
  const headers = {
    "Content-Type": "application/json",
    AppKey: "hidden",  // Replace with actual AppKey
    SecretKey: "hidden"  // Replace with actual SecretKey
  };

  // Make the GET request to the external API
  const response = await fetch(apiUrl, { headers });
  const data = await response.json();

  // Check if the request was successful and if a token was received
  if (data.isSuccessful && data.data && data.data.jwToken) {
    // Return the token and other relevant data
    return json({
      token: data.data.jwToken,
      userName: data.data.userName,
    });
  } else {
    // Handle error or unsuccessful response
    return json({ error: "Token retrieval failed", message: data.message });
  }
};

export default function AuthorizePage() {
  const data = useLoaderData(); // Access the loader data

  return (
    <Page>
      <Layout>
        <Layout.Section>
          <Card sectioned>
            <Heading>Token Retrieval</Heading>
            {data.token ? (
              <div>
                <p><strong>User Name:</strong> {data.userName}</p>
                <p><strong>Bearer Token:</strong> {data.token}</p>
              </div>
            ) : (
              <p>{data.error || "No token found."}</p>
            )}
          </Card>
        </Layout.Section>
      </Layout>
    </Page>
  );
}
aidendev
Shopify Partner
53 1 1

i actually changed my approach after doing some research. apparently if i have a route thats dedicated to making a get request, i can encapsulate that in a function.  then, import said function in another route and use any evenhandler to trigger the call. Im running into errors nonetheless:

 

app.api.authorize.jsx:
import { json } from "@remix-run/node"; // For handling loader data
import { useLoaderData } from "@remix-run/react"; // For accessing data in the component
import Polaris from "@shopify/polaris"; // Import Polaris components
const { Page, Layout, Card, Heading } = Polaris;

export async function fetchToken() {
  // Hardcoded API URL and headers for token retrieval
  const apiUrl = "https://external_api.com/endpoint"; // Replace with actual API URL
  const headers = {
    "Content-Type": "application/json",
    header1: hidden,  // Replace with actual AppKey
    header2: hidden  // Replace with actual SecretKey
  };
  console.log("Sending request to API:");
  console.log("API URL:", apiUrl);
  console.log("Headers:", headers);

  // Make the POST request to the external API
  const response = await fetch(apiUrl, {
    method: "POST",
    headers,
  });
  
  // Parse the response JSON
  const data = await response.json();

  // Return the entire JSON response from the API
  return data;
}

 

then i use this button to try and trigger it:

 

import { fetchToken } from "./app.api.authorize"; // Import the function from app.api.authorize.jsx
import { json } from "@remix-run/node"; // Handle the POST request and return JSON
import { authenticate } from "../shopify.server"; // Assuming authenticate method is defined here

export const loader = async ({ request }) => {
    const { liquid } = await authenticate.public.appProxy(request);
  
    const liquidTemplate = `
      <html>
      <body>
        <h1>Wow {{shop.name}} goes here</h1>
        <button id="fetchToken">Fetch Token</button>
        <p id="responseOutput"></p>
        <script>
          document.getElementById('fetchToken').addEventListener('click', async function() {
            const response = await fetch('', { method: 'post' }); // Use current route
            const data = await response.json();
  
            // Display the entire response
            document.getElementById('responseOutput').innerText = JSON.stringify(data, null, 2);
          });
        </script>
      </body>
      </html>
    `;
  
    return liquid(liquidTemplate);
  };
  
  // Action method to handle POST request and return the full JSON response
  export const action = async () => {
    // Fetch token using the function from app.api.authorize.jsx
    const result = await fetchToken();
  
    // Return the full response without modifying it
    return json(result);
  };

 

this works! the next challenge is seeing if i can call this, use the token to pass as a paraeter to another function that i implemented the same way (importing from another route)