Hi all, I’m building a Shopify embedded app (React + App Bridge) and I’m stuck at a point where calling getSessionToken() just hangs.
Below I’ll explain my setup, what I see in the console, and share the code that runs this part. Any tips welcome!
About my App
It’s a 3PL Fulfillment Dashboard (Vite + React) with these relevant pieces:
Embedded in Shopify Admin (iframe)
Uses Shopify App Bridge (via @shopify/app-bridge-react)
On load: should read the host param, initialize AppBridge, get a session token, verify it with our backend.
Overall Flow
Shopify Admin loads our app with URL like:
https://app.aw-fulfillment.de/app?shop=final-test-shop-123.myshopify.com&host=YWRtaW4uc2hvcGlmeS5jb20vc3RvcmUvZmluYWwtdGVzdC1zaG9wLTEyMw
Our wrapper reads host from URL or localStorage → base64-encodes it if needed → passes it into <AppBridgeProvider>.
Then our AuthProvider calls getSessionToken(appBridge) to get the signed JWT.
That token is POSTed to our backend for verification.
The Actual Problem
The app renders inside Shopify Admin (in iframe), but it gets stuck after logging:
csharp
KopierenBearbeiten
[AuthProvider] Calling getSessionToken...
→ No result.
→ No error.
→ Just hangs forever.
Meanwhile, AppBridge is initialized with the right config.
The host param is there.
AppBridgeProvider renders.
useAppBridge() returns a valid instance.
But calling:
js
KopierenBearbeiten
await getSessionToken(appBridge)
never resolves.
Browser Console (Screenshot)
Relevant Code
index.html
html
KopierenBearbeiten
<meta name="shopify-api-key" content="YOUR_API_KEY" />
<script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>
App.tsx
tsx
KopierenBearbeiten
import React, { useEffect, useMemo, useState } from "react";
import { Provider as AppBridgeProvider } from '@shopify/app-bridge-react';
function ShopifyBridgeWrapper({ children }: { children: React.ReactNode }) {
const [host, setHost] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const hostParam = urlParams.get("host");
if (hostParam) {
localStorage.setItem("shopify_host", hostParam);
setHost(hostParam);
} else {
const storedHost = localStorage.getItem("shopify_host");
if (storedHost) setHost(storedHost);
}
setLoading(false);
}, []);
const isBase64 = (str: string | null) => {
if (!str) return false;
try {
return btoa(atob(str)) === str;
} catch {
return false;
}
};
const appBridgeConfig = useMemo(() => {
if (!host) return null;
const safeHost = isBase64(host) ? host : btoa(host);
return {
apiKey: import.meta.env.VITE_SHOPIFY_API_KEY,
host: safeHost,
forceRedirect: true,
};
}, [host]);
if (loading) {
return <div>Loading Shopify App Bridge...</div>;
}
if (!appBridgeConfig) {
return <div>⚠️ This app requires a valid Shopify host parameter.</div>;
}
return (
<AppBridgeProvider config={appBridgeConfig}>
{children}
</AppBridgeProvider>
);
}
export default ShopifyBridgeWrapper;
AuthContext.tsx (Key Part)
tsx
KopierenBearbeiten
import { useEffect } from 'react';
import { useAppBridge } from '@shopify/app-bridge-react';
import { getSessionToken } from '@shopify/app-bridge-utils';
export function AuthProvider({ children }: { children: React.ReactNode }) {
const appBridge = useAppBridge();
useEffect(() => {
const verifyShopifySessionToken = async () => {
console.log("[AuthProvider] ---- verifyShopifySessionToken started ----");
if (!appBridge) {
console.warn("[AuthProvider] No App Bridge – aborting");
return;
}
try {
console.log("[AuthProvider] Calling getSessionToken...");
const token = await getSessionToken(appBridge);
console.log("[AuthProvider] getSessionToken RESULT:", token);
if (!token) {
console.warn("[AuthProvider] No session token received – aborting");
return;
}
// POST token to backend for verification
// await verifyShopifySession({ token, userId: SYSTEM_USER_ID });
} catch (error) {
console.error("[AuthProvider] Error while calling getSessionToken:", error);
}
console.log("[AuthProvider] ---- verifyShopifySessionToken finished ----");
};
const isEmbedded = window.top !== window.self;
const hostParam = localStorage.getItem('shopify_host');
console.log("[AuthProvider] useEffect started");
console.log("[AuthProvider] Embedded check:", isEmbedded);
console.log("[AuthProvider] hostParam:", hostParam);
if (hostParam && appBridge && isEmbedded) {
verifyShopifySessionToken();
} else {
console.log("[AuthProvider] ⚠️ Aborting – missing prerequisites");
}
}, [appBridge]);
return <>{children}</>;
}
What I already checked
App is installed in Shopify test store
Admin loads the app via Shopify Admin (iframe → embedded)
Correct host param in URL
AppBridgeProvider renders with correct apiKey + host
useAppBridge() returns non-null
What’s strange
getSessionTokensimply never returns (neither resolved nor rejected)- No error thrown
- Browser console only shows:
pgsql
KopierenBearbeiten
WebSocket connection to 'wss://argus.shopifycloud.com/...' failed
But that seems unrelated (Shopify’s analytics socket).
My Questions
- Why would
getSessionToken(appBridge)hang forever? - Is there any Shopify condition that blocks session token generation?
- Do I need to force a redirect manually?
- Could my host param be misformatted even though it’s base64?
- Is there something missing in my AppBridgeProvider config?
- Could it be CSP / iframe / SameSite issues in my server setup?
- Anything else to debug?
Bonus context
- Vite + React 18
- Hosted on https (Supabase Edge Function backend)
- Shopify App Bridge v3
- Works fine if I disable getSessionToken and hardcode the token (so backend verification itself is fine).
Any help or hints very welcome!
