getSessionToken() promise hangs forever in Shopify embedded app

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:
:white_check_mark: Embedded in Shopify Admin (iframe)
:white_check_mark: Uses Shopify App Bridge (via @shopify/app-bridge-react)
:white_check_mark: On load: should read the host param, initialize AppBridge, get a session token, verify it with our backend.

:world_map: Overall Flow

:white_check_mark: Shopify Admin loads our app with URL like:

https://app.aw-fulfillment.de/app?shop=final-test-shop-123.myshopify.com&host=YWRtaW4uc2hvcGlmeS5jb20vc3RvcmUvZmluYWwtdGVzdC1zaG9wLTEyMw

:white_check_mark: Our wrapper reads host from URL or localStorage → base64-encodes it if needed → passes it into <AppBridgeProvider>.

:white_check_mark: Then our AuthProvider calls getSessionToken(appBridge) to get the signed JWT.

:white_check_mark: That token is POSTed to our backend for verification.

:warning: 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.

:magnifying_glass_tilted_right: Browser Console (Screenshot)

:white_check_mark: 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}</>;
}

:white_check_mark: What I already checked

:check_mark: App is installed in Shopify test store
:check_mark: Admin loads the app via Shopify Admin (iframe → embedded)
:check_mark: Correct host param in URL
:check_mark: AppBridgeProvider renders with correct apiKey + host
:check_mark: useAppBridge() returns non-null


:warning: What’s strange

  • getSessionToken simply 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).


:red_question_mark: 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?

:light_bulb: 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!

Thank you for the thorough code samples.

It looks like you’ve added the <script> tag to load App Bridge from https://cdn.shopify.com/shopifycloud/app-bridge.js. However, it looks like you may not have added your API key in the content attribute on <meta> tag.

<meta name="shopify-api-key" content="YOUR_API_KEY" />

Once that’s configured correctly, you shouldn’t need to manually manage your session token.

The current version of App Bridge automatically adds session tokens to requests coming from your app. - Set up session tokens

References

1 Like
I’m encountering an issue with an embedded React app using Shopify App Bridge via the CDN. Specifically, calling window.shopify.idToken() doesn’t behave as expected—the promise neither resolves with a token nor rejects with an error, so it effectively hangs. 
Is there a known issue with the App Bridge CDN,idToken in embedded iframe contexts? Or is there a recommended initialization pattern or polyfill to ensure idToken resolves properly ?
 
For reference, I’m using the following CDN link in the <head> tag of my index.html file:
 

@Bill-Shopify

<meta name="shopify-api-key" content="%REACT_APP_SHOPIFY_API_KEY%" />
<script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>

I’m also using the REACT_APP_SHOPIFY_API_KEY environment variable. When I log window.shopify, I get the expected object, which indicates that App Bridge has been initialized correctly. I also ensure that all API calls are triggered only after window.shopify is available.

Please help—I’m stuck and have already posted about my issues but haven’t received any replies. Any guidance or insight would be greatly appreciated.