Solved

Getting 404 afterAuth on auth/callback

sercanov
Tourist
4 1 2

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)
sercanov
Tourist
4 1 2

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)
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. 

To learn more visit the Shopify Help Center or the Community Blog.

sercanov
Tourist
4 1 2

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!