Remix V4 Embedded App: accounts.shopify.com Refused to Connect Inside Iframe After Scope Changes or Missing Session
Context
Tech Stack
-
Remix V6
-
@shopify/shopify-app-remixv4.1.0 -
Prisma (MySQL)
-
React 18
App Type
- Embedded Shopify App
Shopify Configuration
future: {
unstable_newEmbeddedAuthStrategy: true,
expiringOfflineAccessTokens: true,
}
The Issue
We are encountering an “accounts.shopify.com refused to connect” error inside the Shopify Admin iframe.
This happens in two scenarios:
-
Fresh database / empty Session table
- No Shopify session exists in our database.
-
Scope mismatch
-
App scopes in
shopify.app.tomlwere updated. -
Existing merchant sessions in the database still contain the old scopes.
-
In both cases, the following loader executes:
export const loader = async ({ request }: LoaderFunctionArgs) => {
await authenticate.admin(request);
return json({
apiKey: process.env.SHOPIFY_API_KEY || "",
});
};
authenticate.admin(request) correctly detects that a valid session with the required scopes does not exist and attempts to redirect to /auth.
However, because this occurs while the app is loaded inside the Shopify Admin iframe, the browser ultimately tries to load accounts.shopify.com within the iframe and fails with:
accounts.shopify.com refused to connect
This appears to be caused by Shopify’s security headers (X-Frame-Options / Content-Security-Policy) preventing authentication pages from being rendered inside an iframe.
Relevant Configuration
shopify.server.ts
import { shopifyApp } from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
const shopify = shopifyApp({
apiKey: process.env.SHOPIFY_API_KEY,
apiSecretKey: process.env.SHOPIFY_API_SECRET,
apiVersion: ApiVersion.January25,
scopes: process.env.SCOPES?.split(","),
appUrl: process.env.SHOPIFY_APP_URL || "",
authPathPrefix: "/auth",
sessionStorage: new PrismaSessionStorage(prisma),
future: {
unstable_newEmbeddedAuthStrategy: true,
expiringOfflineAccessTokens: true,
},
});
Questions
-
With
unstable_newEmbeddedAuthStrategy: trueenabled, we expected the Remix package to either:-
Handle authentication inline, or
-
Trigger a top-level redirect using App Bridge to escape the iframe.
Why does
authenticate.admin(request)appear to perform a standard redirect to/auththat ultimately leads to the iframe attempting to loadaccounts.shopify.com? -
-
What is the recommended approach in
@shopify/shopify-app-remixv4 for handling:-
Fresh installs (no stored session)
-
Scope updates requiring reauthorization
without requiring merchants to manually open the app in a top-level browser tab?
-
-
Is there any additional configuration required for the new embedded auth strategy to automatically perform top-level OAuth redirects or use App Bridge’s authentication flow?
Any guidance or examples from apps using the new embedded auth strategy would be greatly appreciated.