ScriptTag returns 403

ValiS
New Member
3 0 1

I'm trying to load a custom JS in the store front, for customers to see, depending on which collections would be selected from the app settings. The issue is that when i try to use ScriptTag, whether for POST, to create a ScriptTag, or GET to read them, i am getting a 403 response.

[Symbol(Response internals)]: {
 url: 'https://agecontrol.myshopify.com/admin/api/2021-07/script_tags.json',
 status: 403,
 statusText: 'Forbidden',
 headers: Headers { [Symbol(map)]: [Object: null prototype] },
 counter: 0
}

 

I am attempting this from the server.js, you can see my code here:

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

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,
  // This should be replaced with your preferred storage strategy
  SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});


// 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 = {};

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;
        ACTIVE_SHOPIFY_SHOPS[shop] = scope;

        const response = await Shopify.Webhooks.Registry.register({
          shop,
          accessToken,
          path: "/webhooks",
          topic: "APP_UNINSTALLED",
          webhookHandler: async (topic, shop, body) =>
            delete ACTIVE_SHOPIFY_SHOPS[shop],
        });

        fetch(`https://${shop}/admin/api/2021-07/script_tags.json`, {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
            'X-Shopify-Access-Token': accessToken,
          }
        }).then(async response => {
          const data = await response.json();
          // check for error response
          console.log(response);
          try {
            if (response.ok) {
              // get error message from body or default to response status
              //    const error = (data && data.message) || response.status;
              //   return Promise.reject(error);
              console.log(`no errorr!!!`);
            }
          } catch (error) {
            this.setState({errorMessage: error.toString()}).bind(this);
            console.error('There was an error!', error);
          }

        }).catch(response => {
          console.log(response);
        });

        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}&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.log(`Failed to process webhook: ${error}`);
    }
  });

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

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

 

I'm attempting this method, following an older ticket:
https://community.shopify.com/c/Shopify-APIs-SDKs/Shopify-App-React-Node-Script-Tag-The-script-tag-i...

Replies 2 (2)
ValiS
New Member
3 0 1

For future reference, the 403 is caused by the lack of scopes for the app. Now, since others might encounter this issue as well, the scopes are found in the .env file of your app. It normally has 

SCOPES=write_products,write_customers,write_draft_orders

I added these as well

SCOPES=write_products,write_customers,write_draft_orders,read_script_tags,write_script_tags

The issue now is that i'm getting a 404 on the ScriptTag loaded. I may not have written the src well

fetch(`https://${shop}/admin/api/2021-07/script_tags.json`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'X-Shopify-Access-Token': accessToken,
          },
          body: JSON.stringify({
            "script_tag": {
              "event": "onload", "src": "https://************.ngrok.io/public/scripttag.js", "display_scope": "all", 'cache': false
            }
          })
        })
add07
New Member
2 0 0

Thanks for the explanation. Did you figure out what's up with the 404 error?