How to call server API from a Page getInitialProps in my Shopify Embedded App using Node, Next.js

Introduction:

I am new to using Node. I have followed the Guide: https://shopify.dev/tutorials/build-a-shopify-app-with-node-and-react

I used the Shopify CLI to create a Node app

I am having problems calling my own API route from my index.js page. I am making a get request to an URL similar to https://123456789.ngrok.io/api/stores/my-development-shop.myshopify.com

I get the following response: Error: Request failed with status code 400 - Bad Request due to somehow my client iframe page that is rendered by index.js has not authenticated.

How do I authenticate my page so I can call my api?

Note: I am able to retrieve the data by typing in my authenticated browser window:

https://my-develop-store.myshopify.com/admin/apps/APP-ID/api/stores/my-develop-store.myshopify.com

I even tried adding the Cookies from the ctx into the header of my request.

Error:

Error: Request failed with status code 400
...
{
  config: {
    url: 'https://123456789.ngrok.io/api/stores/my-develop-store.myshopify.com',
    method: 'get',
    headers: {
      Accept: 'application/json',
      Cookies: 'shopifyNonce=160078696775500;shopifyNonce.sig=5FoocFlgMKZ2YrgmhBqgSN-o0Eg;shopOrigin=my-develop-store.myshopify.com;shopOrigin.sig=6reeQR8RvnjRchwYFuG-kOl480Q;koa:sess=eyJzaG9wIjoicGF5ZGF5LWRldmVsb3AubXlzaG9waWZ5LmNvbSIsImFjY2Vzc1Rva2VuIjoic2hwYXRfOTI1YjExMjdlNWY1NTU3ODNhNWU5N2M5MDJhYjcwNTAiLCJfZXhwaXJlIjoxNjAwODczMzcwMDY0LCJfbWF4QWdlIjo4NjQwMDAwMH0=;koa:sess.sig=1d2AeB5GMmoEBdwD5bB_0MHU8HM',
      'User-Agent': 'axios/0.20.0'
    },
    transformRequest: [ [Function: transformRequest] ],
    transformResponse: [ [Function: transformResponse] ],
    timeout: 0,
    adapter: [Function: httpAdapter],
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    maxBodyLength: -1,
    validateStatus: [Function: validateStatus],
    data: undefined
  },
  request: 

**Files:**

**server/server.js**

```javascript
import "@babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth";
import graphQLProxy, { ApiVersion } from "@shopify/koa-shopify-graphql-proxy";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
import session from "koa-session";
import * as handlers from "./handlers/index";
import { receiveWebhook } from "@shopify/koa-shopify-webhooks";
import { orderPaid } from "../webhooks/orders/paid";
import { customerDataRequest } from "../webhooks/customers/data-request";
import { customerRedact } from "../webhooks/customers/redact";
import { shopRedact } from "../webhooks/shops/redact";
import { getStoreInfoById } from "../api/stores/store";
import fetch from "isomorphic-fetch";

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();
const { SHOPIFY_API_SECRET, SHOPIFY_API_KEY, SCOPES, HOST } = process.env;
app.prepare().then(() => {
  const server = new Koa();
  const router = new Router();
  const webhook = receiveWebhook({
    secret: SHOPIFY_API_SECRET,
  });

  server.use(
    session(
      {
        sameSite: "none",
        secure: true,
      },
      server
    )
  );
  server.keys = [SHOPIFY_API_SECRET];
  server.use(
    createShopifyAuth({
      apiKey: SHOPIFY_API_KEY,
      secret: SHOPIFY_API_SECRET,
      scopes: [SCOPES],

      async afterAuth(ctx) {
        //Auth token and shop available in session
        //Redirect to shop upon auth
        const { shop, accessToken } = ctx.session;
        await handlers.registerWebhooks(
          shop,
          accessToken,
          "ORDERS_PAID",
          "/webhooks/orders/paid",
          ApiVersion.October19
        );

        ctx.cookies.set("shopOrigin", shop, {
          httpOnly: false,
          secure: true,
          sameSite: "none",
        });
        ctx.redirect("/");
      },
    })
  );
  server.use(
    graphQLProxy({
      version: ApiVersion.October19,
    })
  );

  router.get("/", verifyRequest(), async (ctx) => {
    if (typeof ctx.query.shop !== "undefined") {
      await app.render(ctx.req, ctx.res, "/index", ctx.query);
      ctx.respond = true;
      ctx.res.statusCode = 200;
    }
  });

  router.get("/api/stores/:store", verifyRequest(), async (ctx) => {
    // example: /api/stores/my-develop-store.myshopify.com
    // Check if the request is made by the right store
    console.log("Route called: /api/stores/:store");
    const { shop, accessToken } = ctx.session;
    if (ctx.params.store === shop) {
      await getStoreInfoById(ctx)
        .then((storeInfo) => {
          if (typeof storeInfo !== "undefined") {
            ctx.body = JSON.parse(JSON.stringify(storeInfo));
            ctx.set('Content-Type', 'application/json');
            ctx.respond = true;
            ctx.res.statusCode = 200;
          }
        })
        .catch((err) => {
          ctx.res.statusCode = 500;
          console.log(err);
        });
    } else {
      ctx.res.statusCode = 403;
    }
  });

  router.post("/webhooks/orders/paid", webhook, (ctx) => {
    orderPaid(ctx.state.webhook);
    ctx.res.statusCode = 200;
  });

  router.post("/webhooks/customers/data_request", webhook, (ctx) => {
    customerDataRequest(ctx.state.webhook);
    ctx.res.statusCode = 200;
  });

  router.post("/webhooks/customers/redact", webhook, (ctx) => {
    customerRedact(ctx.state.webhook);
    ctx.res.statusCode = 200;
  });

  router.post("/webhooks/shops/redact", webhook, (ctx) => {
    shopRedact(ctx.state.webhook);
    ctx.res.statusCode = 200;
  });

  router.get("*", verifyRequest(), async (ctx) => {
    await handle(ctx.req, ctx.res);
    ctx.respond = false;
    ctx.res.statusCode = 200;
  });
  server.use(router.allowedMethods());
  server.use(router.routes());
  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });
});

pages/index.js

class HomePage extends React.Component {
  // Prepares the props sent to the constructor
  // Server sends the ctx.query as the parameter
  static async getInitialProps(ctx_query) {
    const host = ctx_query.req.headers.host;
    const shop = ctx_query.query.shop;
    // If executed on Server Side
    if (typeof shop === "undefined" || typeof host === "undefined") {
      return {};
    }
    // console.log("host:", host);
    // console.log("shop:", shop);
    // console.log(ctx_query);

    let props = {
      shop: shop,
    };
    // const url_query = ctx_query.asPath.substring(1);
    console.log(url_query);
    const url = "https://" + host + '/api/stores/' + shop;

    console.log("Endpoint URL:", url);
    console.log("");
    axios.get(url, { headers: { 'Accept': 'application/json' } })
      .then((response) => {
        console.log("Response Successful")
        if (typeof response.data !== "undefined") {
          console.log("Data from response:", response.data);
        }
      })
      .catch((error) => {
        // handle error
        console.log(error); // I always get an 400 Bad Request
      })
      .finally(() => {
        // always executed
      });

    return props;
  }
...
}

Solution Found:

I had forgotten a crucial step, and that was to update the environment variable HOST used by the registerWebhooks method.

The reason I was receiving a 403 Error, was because it was receiving I guess the secret from the previous instance of the running app. Maybe someone can back me up on that.

import { registerWebhook } from "@shopify/koa-shopify-webhooks";

export const registerWebhooks = async (
  shop,
  accessToken,
  type,
  url,
  apiVersion
) => {
  const address = `${process.env.HOST}${url}`;
  const registration = await registerWebhook({
    address: address,
    topic: type,
    accessToken,
    shop,
    apiVersion,
  });

  if (registration.success) {
    console.log(`Successfully registered webhook for ${type}!`);
    console.log(`${address}`);
    console.log("");
  } else {
    console.error("Failed to register webhook", registration.result.data.webhookSubscriptionCreate);
  }
};