Getting 404 afterAuth on auth/callback

Solved
sercanov
Tourist
4 1 1

Embedded app installs successfully but it always throws Not Found (404) on route /auth/callback

I've stuck with that problem for weeks. Tried upgrading App Bridge to 2.0, no luck.

I guess, ctx.redirect in afterAuth doesn't work properly. Because I see the logs in afterAuth but not redirecting to shop admin page.

 

The request;

https://shopify.artlabs.ai/auth/callback?code=*******&hmac=*****&host=YXJ0bGFicy1kZXYubXlzaG9waWZ5Lm...

Core dependencies;

 

 

   "@shopify/app-bridge-react": "^2.0.2",
    "@shopify/app-bridge-utils": "^2.0.2",
    "@shopify/koa-shopify-auth": "^4.1.4",

 

 

server.js

 

const app = next({
  dev: __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(
    sessionStorage.storeCallback,
    sessionStorage.loadCallback,
    sessionStorage.deleteCallback
  ),
});

app.prepare().then(async () => {
  const server = new Koa();
  const router = new Router();
  server.keys = [Shopify.Context.API_SECRET_KEY];
  server.use(
    createShopifyAuth({
      accessMode: "online",

      async afterAuth(ctx) {
        // Access token and shop available in ctx.state.shopify
        const { shop, accessToken } = ctx.state.shopify;
        const host = ctx.query.host;

        registerWebhooks({ shop, accessToken });

        graphClient = createClient(shop, accessToken);
        graphClient
          .query({
            query: SHOP_INFO(),
          })
          .then((response) => {
            const { data } = response;

            if (!data) {
              return console.warn("GQL Data ERROR");
            }

            registerShop({
              ...ctx.state.shopify,
              ...data.shop,
              gid: data.shop.id,
            }).then((_shop) => {
              console.log(
                "Shop created on DB..",
                _shop
              );

              ctx.redirect(`/?shop=${shop}&host=${host}`);
            });
          });
      },
    })
  );

 

 

 

_app.js

 

 

import ApolloClient from "apollo-boost";
import axios from "axios";
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, getSessionToken } from "@shopify/app-bridge-utils";
import { Redirect } from "@shopify/app-bridge/actions";
import "@shopify/polaris/dist/styles.css";
import translations from "@shopify/polaris/locales/en.json";
import ClientRouter from "../components/ClientRouter";
import { ShopProvider } from "../context/shop";

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;
  };
}

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

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

  // Create axios instance for authenticated request
  const _axios = axios.create();
  // intercept all requests on this axios instance
  _axios.interceptors.request.use(function (config) {
    return getSessionToken(app).then((token) => {
      // append your request headers with an authenticated token
      config.headers["Authorization"] = `Bearer ${token}`;
      return config;
    });
  });

  const Component = props.Component;

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

class MyApp extends App {
  render() {
    const { Component, pageProps, host } = this.props;

    return (
      <AppProvider i18n={translations}>
        <Provider
          config={{
            apiKey: process.env.API_KEY,
            host: host,
            forceRedirect: true,
          }}
        >
          <ClientRouter />
          <MyProvider Component={Component} {...pageProps} />
        </Provider>
      </AppProvider>
    );
  }
}

MyApp.getInitialProps = async ({ ctx }) => {
  return {
    host: ctx.query.host,
  };
};

export default MyApp;

 

 

Accepted Solution (1)

Accepted Solutions
sercanov
Tourist
4 1 1

This is an accepted solution.

Solved it by moving ctx.redirect to outside of async callback. I think it needs to be executed synchronously inside afterAuth. Thanks!

View solution in original post

Replies 2 (2)
alanthai
Shopify Staff (Retired)
Shopify Staff (Retired)
10 1 1

Hi ,

Is your server.js handling the root path `/`? If so could you show the code so that we may see what might be the issue further down. 

sercanov
Tourist
4 1 1

This is an accepted solution.

Solved it by moving ctx.redirect to outside of async callback. I think it needs to be executed synchronously inside afterAuth. Thanks!