-1

I'm building a Shopify app using the official @shopify/shopify-app-remix framework with PrismaSessionStorage.

In my App Proxy route, I try to send an authenticated Admin GraphQL request using an offline session retrieved via api.session.getOfflineId(shop). But no matter what I try, I always get this error:

GraphQL Error: HttpResponseError: Received an error response (401 Unauthorized) from Shopify: "GraphQL Client: Unauthorized"

This happens even though:

  • The HMAC signature from the App Proxy is verified and valid.
  • The session is successfully retrieved from Prisma using getOfflineId().
  • session.accessToken is present and not expired.
  • The app has been reinstalled after updating scopes.
  • The scopes in shopify.app.toml and .env are correct.
  • Even the simplest GraphQL query fails.

shopify.app.toml

client_id = "..."
name = "Cart Reminder Workflow Trigger"
application_url = "https://my-app-url.trycloudflare.com"
embedded = true

[access_scopes]
scopes = "write_app_proxy,read_customers,write_customers"

[app_proxy]
url = "https://my-app-url.trycloudflare.com/api"
prefix = "apps"
subpath = "cart-reminder"

[auth]
redirect_urls = [
  "https://my-app-url.trycloudflare.com/auth/callback",
  "https://my-app-url.trycloudflare.com/api/auth/callback"
]

shopify.server.ts

import "@shopify/shopify-app-remix/adapters/node";
import {
  ApiVersion,
  AppDistribution,
  shopifyApp,
} from "@shopify/shopify-app-remix/server";
import { shopifyApi } from "@shopify/shopify-api";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import prisma from "./db.server";

const sessionStorage = new PrismaSessionStorage(prisma);
const appUrl = process.env.SHOPIFY_APP_URL || "";
const url = new URL(appUrl);

const shopify = shopifyApp({
  apiKey: process.env.SHOPIFY_API_KEY!,
  apiSecretKey: process.env.SHOPIFY_API_SECRET!,
  apiVersion: ApiVersion.January25,
  scopes: process.env.SCOPES!.split(","),
  appUrl,
  authPathPrefix: "/auth",
  sessionStorage,
  distribution: AppDistribution.AppStore,
  future: {
    unstable_newEmbeddedAuthStrategy: true,
    removeRest: true,
  },
});

export const api = shopifyApi({
  apiKey: process.env.SHOPIFY_API_KEY!,
  apiSecretKey: process.env.SHOPIFY_API_SECRET!,
  apiVersion: ApiVersion.January25,
  scopes: process.env.SCOPES!.split(","),
  hostName: url.hostname,
  hostScheme: url.protocol.replace(":", "") as "http" | "https",
  isEmbeddedApp: true,
});

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

App Proxy Route (app/routes/api.backend-collector.tsx)

const sessionId = api.session.getOfflineId(shop);
const session = await shopify.sessionStorage.loadSession(sessionId);

if (!session || !session.accessToken) {
  return json({ error: "Unauthorized" }, { status: 401 });
}

const admin = new api.clients.Graphql({ session });

const result = await admin.request(`{ shop { name } }`);

Debugging Summary:

  • The session is loaded via getOfflineId(), and the token is present.
  • The access scopes are included in shopify.app.toml and match .env.
  • The app was reinstalled after changing scopes.
  • HMAC signature from App Proxy is valid and verified.

What could cause my 401 from the GraphQL Admin API when using an offline session?

1 Answer 1

0

Well the correct way to do it is in the route you want the graphql to run to do

  const {admin} = await shopify.authenticate.admin(request);

and then use admin.graphql to run your query for that particular shop.

The request you get it from the loader when you load a page inside your embedded app on shopify.

SessionStorage is mainly used for validating the session coming from the request and updating it.

See more here how to do graphql queries inside your app

https://shopify.dev/docs/api/shopify-app-remix/v2/authenticate/admin

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.