Call authenticated custom api endpoint in app from client side

Hi!

I’m making a Shopify app with the Shopify CLI as a scaffold. I’m trying to create a callable api endpoint that is authenticated, I’m quite new to shopify and Koa but this is what I’ve got so far.

This is the server.js (I removed some irrelevant stuff)

import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth";

// ... setup context, etc

app.prepare().then(async () => {
  const server = new Koa();
  const router = new Router();
  server.keys = [Shopify.Context.API_SECRET_KEY];
  server.use(
    createShopifyAuth({
      async afterAuth(ctx) {
        // Access token and shop available in ctx.state.shopify
        const { shop, accessToken, scope } = ctx.state.shopify;
        const host = ctx.query.host;
        // ... do stuff here
        // Redirect to app with shop parameter upon auth
        ctx.redirect(`/?shop=${shop}&host=${host}`);
      },
    })
  );

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

  router.post("/webhooks", async (ctx) => {
    try {
      await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
      console.log(`Webhook processed, returned status code 200`);
    } catch (error) {
      console.error(`Failed to process webhook: ${error}`);
    }
  });

  router.post(
    "/graphql",
    verifyRequest({ returnHeader: true }),
    async (ctx, _next) => {
      await Shopify.Utils.graphqlProxy(ctx.req, ctx.res);
    }
  );

  router.post("/api/register", verifyRequest(), async (ctx) => {
    // do db calls with data from request
  });

  router.get("(/_next/static/.*)", handleRequest); // Static content is clear
  router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
  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(router.allowedMethods());
  server.use(router.routes());
  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });
});

Here the important part is the /api/register route

router.post("/api/register", verifyRequest(), async (ctx) => {
  // do db calls with data from request
});

From what I understand the verifyRequest() function is looking for the accessToken in the request.

Here is a simplified version of the frontend code, basically I’m just trying to call the endpoint with a simple fetch call.

const Index = () => {
  const handleSubmit = React.useCallback(() => {
    fetch("/api/register", {
      method: "POST",
    });
  }, []);

  return (
    
  );
};

What happens with this setup is that the authentication in verifyRequest() fails and it redirects to /auth endpoint which does not have a shop property so it just returns a 400.

My question is how can I get the access token on the frontend so I can pass it in the request so the authentication does not fail? Or is there a better way of doing this?

Some of the posts and solutions I have looked at so far:

Returns Error 405 On Embedded Node.Js App

React/Node Embedded App, GET Call To Online Store API

Error 400 ‘Required Parameter Missing Or Invalid’ When Generating A Storefront Access Token

Thanks!

Found the solution, even though for some reason most of the documentation dissappeared about this or at least I found a lot of links pointing to nothing on the shopify dev sites.

import { Heading, Page } from "@shopify/polaris";
import React from "react";
import { authenticatedFetch } from "@shopify/app-bridge-utils";
import { useAppBridge } from "@shopify/app-bridge-react";

const Index = () => {
  const app = useAppBridge();
  const useFetch = authenticatedFetch(app);

  const handleSubmit = React.useCallback((e) => {
    e.preventDefault();
    useFetch("/api/register");
  }, []);

  return (
    
  );
};

export default Index;

There is a build in function for the authentication that you need to pass the app which you can get from the useAppBridge hook.

router.post("/api/register", verifyRequest(), async (ctx) => {
  const session = await Shopify.Utils.loadCurrentSession(ctx.req, ctx.res);
  console.log("Session:", session);
});

From there you can get the session and the associated information with the Shopify.Utils.loadCurrentSession() function.