koa-shopify-auth icon indicating copy to clipboard operation
koa-shopify-auth copied to clipboard

Multi page server side rendered NextJS app fails to open other pages

Open avocadoslab opened this issue 3 years ago • 45 comments

Issue summary

I've managed my existing app to migrate to use sessions instead of cookies and initial index page loads fine. When I click on another menu items within embedded app, it re-initiates auth process and kicks ma back on index page.

image

Main Packages:

"@shopify/koa-shopify-auth": "^4.0.3"
"@shopify/app-bridge-utils": "^1.29.0"
"@koa/router": "^8.0.8"
"next": "^10.0.9"
"shopify-api-node": "^3.6.6"

Expected behavior

Should be able to visit other pages within app.

Actual behavior

When I click on other navigation items, does re-auth and kick me back to index page when I click on any navigation item

Code

server.js
import "@babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth";
import Shopify, { ApiVersion } from "@shopify/shopify-api";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
import { MongoClient } from "mongodb";

dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8081;
const dev = process.env.NODE_ENV !== "production";
const app = next({
    dev,
});
const handle = app.getRequestHandler();

Shopify.Context.initialize({
    API_KEY: process.env.SHOPIFY_API_KEY,
    API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
    SCOPES: process.env.SCOPES.split(","),
    HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
    API_VERSION: ApiVersion.October20,
    IS_EMBEDDED_APP: true,
    SESSION_STORAGE: new Shopify.Session.CustomSessionStorage(
        // not including implementation to save some space
        storeCallback,
        loadCallback,
        deleteCallback
    )
});

// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};

MongoClient.connect(MONGODB_URI, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
}).then(async connection => {
    console.info(`Successfully connected to Mongo`);
    const mongodb = connection;

    app.prepare().then(async () => {
        const server = new Koa();
        const router = new Router();
        server.keys = [Shopify.Context.API_SECRET_KEY];
        server.use(
            createShopifyAuth({
                apiKey: SHOPIFY_API_KEY,
                secret: SHOPIFY_API_SECRET,
                scopes: [SCOPES],
                accessMode: "offline",
                async afterAuth(ctx) {
                    // Access token and shop available in ctx.state.shopify
                    const { shop, accessToken, scope } = ctx.state.shopify;
                    ACTIVE_SHOPIFY_SHOPS[shop] = scope;

                    if (!response.success) {
                        console.log(
                            `Failed to register APP_UNINSTALLED webhook: ${response.result}`
                        );
                    }

                    // Redirect to app with shop parameter upon auth
                    ctx.redirect(`/?shop=${shop}`);
                },
            })
        );

        const handleRequest = async (ctx) => {
            await handle(ctx.req, ctx.res);
            ctx.respond = false;
            ctx.res.statusCode = 200;
        };

        router.get("/", async (ctx) => {
            const shop = ctx.query.shop;

            // This shop hasn't been seen yet, go through OAuth to create a session
            if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
                ctx.redirect(`/auth?shop=${shop}`);
            } else {
                await handleRequest(ctx);
            }
        });

        server.use(require("./api"));

        router.get("(/_next/static/.*)", handleRequest); // Static content is clear
        router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
        router.get("(.*)", verifyRequest({ accessMode: "offline" }), handleRequest); // Everything else must have sessions

        server.use(router.allowedMethods());
        server.use(router.routes());
        server.listen(port, () => {
            console.log(`> Ready on http://localhost:${port}`);
        });
    });
});
_app.js
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
import App from "next/app";
import { AppProvider } from "@shopify/polaris";
import { Provider, useAppBridge } from "@shopify/app-bridge-react";
import { authenticatedFetch } from "@shopify/app-bridge-utils";
import "@shopify/polaris/dist/styles.css";
import translations from "@shopify/polaris/locales/en.json";

function MyProvider(props) {
  const app = useAppBridge();

  const client = new ApolloClient({
    fetch: authenticatedFetch(app),
    fetchOptions: {
      credentials: "include",
    },
  });

  const Component = props.Component;

  return (
    <ApolloProvider client={client}>
      <Component {...props} />
    </ApolloProvider>
  );
}

class MyApp extends App {

  render() {
    const { Component, pageProps, shopOrigin } = this.props;
    return (
      <AppProvider i18n={translations}>
        <Provider
          config={{
            apiKey: API_KEY,
            shopOrigin: shopOrigin,
            forceRedirect: true,
          }}
        >
          <MyProvider Component={Component} {...pageProps} />
        </Provider>
      </AppProvider>
    );
  }
}

MyApp.getInitialProps = async ({ ctx }) => {
  return {
    shopOrigin: ctx.query.shop,
    API_KEY: process.env.SHOPIFY_API_KEY
  };
};

export default MyApp;

Checklist

  • [X] Please delete the labels section before submitting your issue
  • [X] I have described this issue in a way that is actionable (if possible)

avocadoslab avatar Mar 21 '21 00:03 avocadoslab

My problem is the same as yours, whenever I press one of the navigation items the app goes through the auth process all over again and displays the main page, if I add a route like this:

router.get("/index", handleRequest);

Then it works, but I think that's not the right way to do it since the request wouldn't be verified, and adding router.get("/index", verifyRequest({ accessMode: 'offline'}), handleRequest); has the same result as not having a route, the auth process happens all over again.

I tried to follow the tutorial in the Shopify docs, about how to build your user interface with Polaris, in which they tell you to add a ClientRouter component but adding it didn't solve the issue, I actually tested the code on that branch but the code doesn't seem to work, because whenever I press one of the navigation items, I get a Bad request message in the embedded app and nothing happens, I don't know what could be the issue, I've been searching for a solution but no luck yet.

ilugobayo avatar Mar 21 '21 14:03 ilugobayo

Confirm it! In my opinion, the problem is related to the auth token is lost after route to index page. need to save it and use it for following routing. But I still have no idea how.... any ideas?

valorloff avatar Mar 21 '21 18:03 valorloff

I was able to replicate this on current https://github.com/Shopify/shopify-app-node with the help of Shopify CLI. If you aren't using Shopify CLI, you can simply clone the project and follow setup instructions.

Steps to reproduce:

  1. shopify create project jwt_test
  2. shopify serve
  3. Begin app installation on development store.
  4. App does authenticate well and loads index page fine. (It's damn slow btw as mentioned in https://github.com/Shopify/shopify-app-node/issues/584)
  5. Go back to codebase and create pages/indexCopy.js and just copy paste contents of pages/index.js in it.
  6. Go to partners dashboard and add a navigation menu for newly created app.
  7. Navigate to app and then navigate to index copy
  8. You would see it would re-authenticates again and then kicks you back to index.js

image

image

avocadoslab avatar Mar 21 '21 18:03 avocadoslab

Confirmed! App does authenticate well and loads index page fine, but other navigations results 'bad request'. Reload page results 'InternalServerError: Cannot complete OAuth process. Could not find an OAuth cookie for shop url:' and so on

valorloff avatar Mar 21 '21 20:03 valorloff

@paulomarg could you please shed some light on this matter? It would be highly appreciated.

ilugobayo avatar Mar 22 '21 10:03 ilugobayo

I also faced the same issue, when I navigate to other pages except index, it simply shows "bad request". This can be simply replicated by adding new pages on starter app created by the latest shopify cli. Please advise.

tommy921092 avatar Mar 23 '21 02:03 tommy921092

Quick update: This seems to be the issue around verifyRequest middleware. Following doesn't fix the re-auth issue but at least loads navigated page after 10 seconds or so rather redirecting back to index.

Change

router.get("(.*)", verifyRequest({ accessMode: "offline" }), handleRequest);

to

router.get("(.*)", handleRequest);

Note: This isn't recommended solution.

@paulomarg would be great if you can look into this at your earliest convenience. Thanks a lot in advance :)

avocadoslab avatar Mar 24 '21 01:03 avocadoslab

Hey folks! I think @pratiknikam is right that verifyRequest would trigger that behaviour if your app is using server-rendered pages, because it would expect to find the JWT in the Authorization header, if that request made it to the server - which won't happen for page navigation.

As mentioned in the tutorial, the ClientRouter will prevent your app from making such requests by handling URL changes locally.

Note: I suspect the tutorial's code for that step might be using a slightly outdated version of koa-shopify-auth. I'll make sure it's working as expected so you can use it as an example.

If you have to do a page reload when the URL changes, you should consider the following:

  • That action should behave like the / endpoint, so it doesn't use verifyRequest, but it should make sure to check ACTIVE_SHOPIFY_SHOPS if the user can go straight to that page.
  • Not using verifyRequest means the endpoint is unauthenticated, so it should not load any sensitive information because it may be accessed by other users. You should load a page skeleton and use authenticatedFetch to grab any sensitive data.
  • You may need to make sure the shop param is in the URL

Update: I've tested the tutorial branch and the routing worked as expected. If you're trying it out, make sure you run npm install and set up the .env file.

paulomarg avatar Mar 24 '21 13:03 paulomarg

@paulomarg thanks for additional helpful insights :) Adding ClientRouter did help me here. Another question I have is around navigations which happen on client side.

If you look at following Polaris component. Whenever merchant clicks on "Create now", that throws 302 Found with Location being /auth route which then throws 400.

<CalloutCard
    title="Create Element"
    primaryAction={{
        content: "Create now",
        url: "/element/new"
 }}
>

image

avocadoslab avatar Mar 25 '21 01:03 avocadoslab

@pratiknikam can you share the code that worked for you? Last week I tried with the ClientRouter component for various days to make it work but I just couldn't, guess I'm doing something wrong

ilugobayo avatar Mar 25 '21 01:03 ilugobayo

the clientRouter in the tutorial code was originally, this does not affect in any way, problem does't solved! After install app succesfully loaded index page, following navigations by nav links impossible, anybody confirm it?

valorloff avatar Mar 25 '21 07:03 valorloff

@valorloff I was originally missing ClientRouter. After adding it like below, I can at least navigate to different pages. I'm still having an issue I mentioned just couple threads above though around client side navigation. URL doesn't change when I try to navigate client side with the help of Router or any Polaris component.

Updated _app.js
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
import App from "next/app";
import { AppProvider } from "@shopify/polaris";
import { Provider, useAppBridge } from "@shopify/app-bridge-react";
import { authenticatedFetch } from "@shopify/app-bridge-utils";
import "@shopify/polaris/dist/styles.css";
import translations from "@shopify/polaris/locales/en.json";
import ClientRouter from "../components/ClientRouter";

function MyProvider(props) {
  const app = useAppBridge();

  const client = new ApolloClient({
    fetch: authenticatedFetch(app),
    fetchOptions: {
      credentials: "include",
    },
  });

  const Component = props.Component;

  return (
    <ApolloProvider client={client}>
      <Component {...props} />
    </ApolloProvider>
  );
}

class MyApp extends App {

  render() {
    const { Component, pageProps, shopOrigin } = this.props;
    return (
      <AppProvider i18n={translations}>
        <Provider
          config={{
            apiKey: API_KEY,
            shopOrigin: shopOrigin,
            forceRedirect: true,
          }}
        >
          <ClientRouter />
          <MyProvider Component={Component} {...pageProps} />
        </Provider>
      </AppProvider>
    );
  }
}

MyApp.getInitialProps = async ({ ctx }) => {
  return {
    shopOrigin: ctx.query.shop,
    API_KEY: process.env.SHOPIFY_API_KEY
  };
};

export default MyApp;

avocadoslab avatar Mar 25 '21 12:03 avocadoslab

@pratiknikam The 302 response from the first request means it was redirected to /auth, most likely because of verifyRequest.

Looking at your updated _app file, I think you need to move your <ClientRouter /> line one level up, so it's between <AppProvider> and <Provider>.

paulomarg avatar Mar 25 '21 15:03 paulomarg

Routing like redirect.dispatch(Redirect.Action.APP, {path: '/edit-collection', newContext: true}); works ok, but routing by nav links return bad request crash. Do I understand correctly that the clientRouter makes Embedded App Navigation links client-side? Should the server.js be involved when clicked Navigation links?

valorloff avatar Mar 25 '21 17:03 valorloff

@paulomarg good catch! Now next issue I'm having is that page URL is not updating with page I'm navigating to. These are pages which are not set in app navigation but part of app. (Ex. Creating new block)

Agree with what @valorloff said around navigation. I'm seeing similar issue.

Doesn't work: (does redirect but doesn't update URL)

router.push(`/element/new`);

or

<CalloutCard
    title="Create Element"
    primaryAction={{
        content: "Create now",
        url: "/element/new"
 }}
>

Does work: (does redirect and also updates URL)

redirect.dispatch(Redirect.Action.APP, {path: '/element/new', newContext: true});

avocadoslab avatar Mar 25 '21 18:03 avocadoslab

@pratiknikam you seem to have solved a majority of the issue, could you share your ClientRouter Component. I can't seem to figure out which packages you are using for the redirect and for the Redirect

are you using these packages? import redirect from "@shopify/koa-shopify-auth" import {Redirect} from "@shopify/app-bridge/actions"

Crunchyman-ralph avatar Mar 25 '21 18:03 Crunchyman-ralph

My issue with "index" page solved thanks to this hint But that's only half of the problem. Remain problem of devServer page reload during development process, which throws out GET https://ngrokXXX/auth 400 (Bad Request)

valorloff avatar Mar 25 '21 21:03 valorloff

You need two things, the ClientRouter component and RoutePropagator


import React from 'react';
import { withRouter } from 'next/router';
import { ClientRouter as AppBridgeClientRouter } from '@shopify/app-bridge-react';

function ClientRouter(props) {
  const { router } = props;
  return <AppBridgeClientRouter history={router} />;
}

export default withRouter(ClientRouter);
import { useAppBridge, RoutePropagator as ShopifyRoutePropagator } from '@shopify/app-bridge-react';

const RoutePropagator = () => {
  const router = useRouter();
  const { route } = router;
  const app = useAppBridge();

  useEffect(() => {
    app.subscribe(Redirect.ActionType.APP, ({ path }) => {
      Router.push(path);
    });
  }, []);

  return appBridge && route ? (
    <ShopifyRoutePropagator location={route} />
  ) : null;
};

This has a tradeoff, you will lose the query with hmac, shop and other params when you navigate to other pages but you can grab that from the session object.

ivorpad avatar Mar 26 '21 15:03 ivorpad

a[[.subscribe(Redirect.ActionType.APP, ({ path }) => {
      Router.push(path);
    });

Hi! Please, explain this section Maybe, appBridge is app and Router is router?

valorloff avatar Mar 26 '21 19:03 valorloff

@valorloff A typo. Fixed.

ivorpad avatar Mar 26 '21 19:03 ivorpad

Did you mean add RoutePropagator to _app.js?

<Provider config={config}>
                    <RoutePropagator />
                    <ClientRouter/>
                    <AppProvider i18n={translations}>
                        <MyProvider>
                            <Component {...pageProps} />
                        </MyProvider>
                    </AppProvider>
 </Provider>

This works nice (including browser reload) until I make a change to the code and the devServer does restart (FastRefresh). After devServer restart, in particular on '/' path, we get crush with GET https://ngrok/auth 400 (Bad Request) error

valorloff avatar Mar 26 '21 19:03 valorloff

@ivorpad did you mean to replace appBridge by app in return appBridge && route ? ( <ShopifyRoutePropagator location={route} /> ) : null; making it return app && route ? ( <ShopifyRoutePropagator location={route} /> ) : null;

and where do you inject this component ? in the _app.js next to the <ClientRouter/> ?

Crunchyman-ralph avatar Mar 27 '21 14:03 Crunchyman-ralph

Although i followed above suggestions, still I'm not able to do routing by using below app bridge action.

Redirect.create(app).dispatch(Redirect.Action.APP, { path: redirectionUrl, newContext: true });

The weird part is i can't see above dispatched action when i checked redux devtools for app bridge.

import {useEffect, useContext} from 'react';
import Router, { useRouter } from "next/router";
import { Context as AppBridgeContext } from "@shopify/app-bridge-react";
import { Redirect } from "@shopify/app-bridge/actions";
import { RoutePropagator as ShopifyRoutePropagator } from "@shopify/app-bridge-react";

const RoutePropagator = () => {
  const router = useRouter(); 
  const { route } = router;
  const appBridge = useContext(AppBridgeContext);

  useEffect(() => {
    appBridge.subscribe(Redirect.ActionType.APP, ({ path }) => {
      Router.push(path);
    });
  }, []);

  return appBridge && route ? (
    <ShopifyRoutePropagator location={route} app={appBridge} />
  ) : null;
}

export default RoutePropagator;
import { withRouter } from 'next/router';
import {ClientRouter as AppBridgeClientRouter} from '@shopify/app-bridge-react';

function ClientRouter(props) {
  const {router} = props;
  return <AppBridgeClientRouter history={router} />;
};

export default withRouter(ClientRouter);
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
import App from "next/app";
import { AppProvider } from "@shopify/polaris";
import { Provider, Context } from "@shopify/app-bridge-react";
import { Redirect } from '@shopify/app-bridge/actions';
import { authenticatedFetch } from "@shopify/app-bridge-utils";
import "@shopify/polaris/dist/styles.css";
import translations from "@shopify/polaris/locales/en.json";
import ClientRouter from '../components/ClientRouter';
import RoutePropagator from '../components/RoutePropagator';
import { Provider as ReactReduxProvider } from 'react-redux';
import { useStore } from '../store/store';

function userLoggedInFetch(app) {
  const fetchFunction = authenticatedFetch(app);

  return async (uri, options) => {
    const response = await fetchFunction(uri, options);

    if (response.headers.get('X-Shopify-API-Request-Failure-Reauthorize') === '1') {
      const authUrlHeader = response.headers.get('X-Shopify-API-Request-Failure-Reauthorize-Url');

      const redirect = Redirect.create(app);
      redirect.dispatch(Redirect.Action.APP, authUrlHeader || `/auth`);
      return null;
    }

    return response;
  };
}

class MyProvider extends React.Component {
  static contextType = Context;

  render() {
    const app = this.context;

    const client = new ApolloClient({
      fetch: userLoggedInFetch(app),
      fetchOptions: {
        credentials: "include",
      },
    });

    return (
      <ApolloProvider client={client}>
        {this.props.children}
      </ApolloProvider>
    );
  }
}

class MyApp extends App {
  render() {
    const { Component, pageProps, shopOrigin } = this.props;
    
    const config = { apiKey: API_KEY, shopOrigin, forceRedirect: true };
    const store = useStore({});

    return (
      <Provider config={config}>
        <ClientRouter />
        <AppProvider i18n={translations}>
          <RoutePropagator />
          <ReactReduxProvider store={store}>
            <MyProvider>
              <Component {...pageProps} />
            </MyProvider>
          </ReactReduxProvider>
        </AppProvider>
      </Provider>
    );
  }
}

MyApp.getInitialProps = async ({ ctx }) => {
  return {
    shopOrigin: ctx.query.shop,
    API_KEY: process.env.SHOPIFY_API_KEY
  };
};

export default MyApp;

barisozdogan avatar Mar 29 '21 15:03 barisozdogan

I think the class components should be redone to functional for comfortable use hooks

valorloff avatar Mar 29 '21 17:03 valorloff

@barisozdogan I decided to not use the built in shopify navigation and instead use my own polaris navigation. This is what the code looks like

import {useRouter} from 'next/router'

const router = useRouter()
Router.push({
        pathname: [enter your url],
        query: router.query
    })

Crunchyman-ralph avatar Mar 30 '21 18:03 Crunchyman-ralph

Well, I finally was able to make the tutorial code work, but now trying to implement that solution in my app gives me the next problem. Because I need to load some data from the server for which I use my custom routes, namely

On server.js:

router.get('/api/get-paid-status', async (ctx) => {
const referer = ctx.headers.referer;
    const shopDomain = referer.substring(referer.indexOf('&shop=') + 6, referer.indexOf('&timestamp'));

    try {
      const paidStatus = await dbGetHelper.getPaidStatus(shopDomain);

      if (paidStatus !== undefined && paidStatus !== null) {
        ctx.body = {
          data: paidStatus
        };
      } else {
        ctx.body = {
          data: null
        };
        logger.error(stringInject.default(logMessages.serverPaidStatusUndefined, [shopDomain]));
      }
    } catch (err) {
      ctx.body = {
        data: null
      };
      logger.error(stringInject.default(logMessages.serverGetPaidStatusCatch, [shopDomain, err]))
    }
  });

Which I call from a page, in this case, a page that is not index, backfill.js

const paidStatus = await this.makeGetRequest('/api/bayonet/get-paid-status/');

The makeGetRequest is as follows to hit the route in the server

makeGetRequest = async (endpoint) => {
    const result = await fetch(endpoint,
      {
        method: 'GET',
        headers: {
          'Accept': 'application/json',
        },
      })
      .then(resp => resp.json())
      .then(json => {
        return json;
      });

    return result;
  };

If the app has been just loaded, it will show the index page with no issues and every request to the server will work as expected, the referer will contain the shopOrigin and everything, no issues at all, but if I try to use the navigation buttons, nothing will load, somehow I lose the authentication/session info?

I think this is where the problem lies, because checking the ngrok log and the console on Chrome, it tries to go to the auth page all over again, so my skeleton page never disappears, and as you can see, since I started to develop the app I had been using ctx.headers.referer to get the shopOrigin and then proceed to perform the corresponding tasks, but now with the ClientRouter whenever I press on the navigation buttons, including the one linked to the '/' endpoint, the referer only has the URL of my server and the endpoint, namely https://mylocalserver.ngrok.io/backfill, no hmac, no shop, nothing.

If I try to go directly to the page from the address bar, not using the navigation buttons, I get InternalServerError: Cannot complete OAuth process. Could not find an OAuth cookie for shop url

I've been searching for so many days for a clear example on how to handle this, since the session storage has been already addressed and solved, this is the only part that I'm missing to make my app work again like it used to before the required changes for the app review.

I would endlessly appreciate anyone that could guide me on this, thank you.

ilugobayo avatar Mar 31 '21 12:03 ilugobayo

@ilugobayo Did you upgrade to latest version of koa-shopify-auth: 4.1.2? If so, please delete your iFrame cookies. I saw similar issue and clearing cookies did help me resolve it.

avocadoslab avatar Mar 31 '21 12:03 avocadoslab

@pratiknikam Just upgraded the koa-shopify-auth version and tried to test it using incognito mode, to avoid any old cookies stored, same situation. Do you make any requests to the server from any of your pages? If so, how did you manage to authenticate them?

ilugobayo avatar Mar 31 '21 12:03 ilugobayo

I finally fixed my problem. Let me briefly explain here as well, as someone might be experiencing same situation.

In my case, the problem was came out during callback from twitter. I defined a route within server.js to be able to handle twitter callback request.

const handleRequest = async (ctx) => {
   await handle(ctx.req, ctx.res);
   ctx.respond = false;
   ctx.res.statusCode = 200;
 };

router.get("/oauth/callback/twitter", async (ctx) => {
   const shop = ctx.cookies.get('shop');
   ctx.redirect(`/auth?shop=${shop}`);
   ctx.request.url = ctx.request.url + "&shop=" + shop
   await handleRequest(ctx);
 }); 

The problem for that route was to not checking whether auth completed successfully or not right after redirect. Because once oauth completed, it will be hit the same route again along the known parameters such as "hmac" and other stuff. So if i don't check hmac, it would be trying to re-authenticate again for an already authenticated request and it will end up with recursion. Corresponding nextjs page would be shown for this route at the end but shopify app bridge could not be initialized properly. This will lead to dispathing a Redirect action to the non created app bridge instance. When I check redux dev tools for the app bridge's state, I was not able to see dispatched redirect action which proofs my case.

In order to fix issue i changed my route as below;

router.get("/oauth/callback/twitter", async (ctx) => {
    if (!ctx.request.query.hmac) {
      const shop = ctx.cookies.get('shop');
      ctx.redirect(`/auth?shop=${shop}`);
      ctx.request.url = ctx.request.url + "&shop=" + shop
    }
    await handleRequest(ctx);
  });

After this, I was able to monitor that app bridge created properly and Redirect started to working (Also observed successfull dispatched redirect action to the app bridge state within redux devtools) I hope it can help to your problems as well.

Thanks,

barisozdogan avatar Apr 01 '21 12:04 barisozdogan

@barisozdogan did you test your app on Safari by turning off third party cookies? Reason I ask is because I see ctx.cookies.get. If app doesn't have access to iFrame cookies, not sure where ctx would store or fetch it from.

avocadoslab avatar Apr 01 '21 22:04 avocadoslab