Shopify App Proxy with Remix (Notes)

Hello all,

I found the app proxy docs to be quite confusing so I thought I would detail the things I did to set up an app proxy page with a fresh remix project. (especially the steps that weren’t obvious). I’m not completely sure that all these steps are totally necessary but they’re the steps I took to get it working.

  1. Add proxy to toml:
[app_proxy]
url = "<domain or cloudflare tunnel>"
subpath = "<whatever>"
prefix = "a/apps/tools"
  1. Setup Cloudflare tunnel in Cloudflare zero trust dashboard, connect, run tunnel (just look up how to run Cloudflare tunnel if unfamiliar).

  2. Try your _index page on a test store. Go to domain .com/<<prefix/<<subpath and see if it shows up. If your styling isn’t showing up, you may need to remove ?url from your css routes/imports.

**A big issue I had was that I had to specify that the routes had to start with /<<prefix/<<subpath. I fixed this by adding a remix.config.js file and adding the link prefix as a base. This is what the file looked like:

/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
    base: '/<<prefix/<<subpath',
    ignoredRouteFiles: ['**/.*'],
    serverModuleFormat: 'cjs',
};
  1. Next, I had to figure out how to make http requests. This was by far the most frustrating part. There’s a specific flow that Shopify wants you to follow and they have docs about it but I found them quite confusing so I will explain what I did here.
    First, you must add a shopify.server.js file. This was mine:
import "@shopify/shopify-app-remix/adapters/node";
import {
    ApiVersion,
    shopifyApp,
} from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

const shopify = shopifyApp({
    apiKey: process.env.SHOPIFY_API_KEY,
    apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
    apiVersion: ApiVersion.October24,
    scopes: process.env.SCOPES?.split(","),
    appUrl: process.env.SHOPIFY_APP_URL || "",
    authPathPrefix: "/auth",
    sessionStorage: new PrismaSessionStorage(prisma),
});

export default shopify;
export const authenticate = shopify.authenticate;
export const sessionStorage = shopify.sessionStorage;

It’s very important that you install everything and have your Prisma database correctly connected. Also you must add a Session table to your prisma database otherwise PrismaSessionStorage won’t work. Here’s the Session table I used:

model Session {
  id          String    @id
  shop        String?
  state       String?
  isOnline    Boolean?
  scope       String?
  expires     DateTime?
  accessToken String?
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt
}

From there, you can set up your request. I just made a button with method=“post” and action=“/<<prefix/<<subpath/login”. Note: you can’t make a request to /<<prefix/<<subpath because it will try to make the request to your /root file instead of your _index file.

In your action function, add await authenticate.public.appProxy(request); just so Shopify will let you make an http request. If I’m not mistaken, Shopify uses params in the url to authenticate a request so that’s why you may see Shopify trying to navigate to a weird url. The params will be in this format:

    const url = new URL(request.url);
    const shop = url.searchParams.get("shop");
    const timestamp = url.searchParams.get("timestamp");
    const signature = url.searchParams.get("signature");

From there you can make whatever requests you like.

I apologize if any of this information is incorrect, I just cared to recount the process I used. Perhaps someone will find it useful, I know I will at least go back and refer to this. Cheers!

1 Like

Hey Beckett!

Thanks for documenting your approach here - will be super valuable for devs who are working in this space.

1 Like

Another thing: auth/login flow

Was difficult to think of an auth/login flow that was good so sharing what I came up with here.

Shopify doesn’t let you create cookies so you can’t use your own auth… well you can but you would just have to have your auth id in the URL which means anyone with that url can see stuff.

They want you to use customer accounts to handle auth. But how do you allow people to create an account specific to your app without applying for the read_customers scope?

This is my flow:

• Start with this to get logged_in_customer_id

        await authenticate.public.appProxy(request);

        const url = new URL(request.url);
        const loggedInCustomerId = url.searchParams.get("logged_in_customer_id");

Next, I had this logic flow (slightly complicated, prob best to just copy and paste into a chatbot):

> if loggedInCustomerId exists:
> check your database for user where loggedInCustomerId matches
> if no, take to register page. Include loggedInCustomerId upon registration, registration should fail without it.
> if yes, user authenticated so login.

> if loggedInCustomerId doesn’t exist
> login button and register button (register grayed out, “to register, please sign in with domain .com/account”), login button navigates to domain .com/account