Fixing "frame-ancestors directive" Errors on Shopify Embedded Apps
Tags: webdev, shopify, ecommerce, node
Published: 2022-08-22 07:40:06

Building a Shopify app can be complex, and you might face many challenges during the process. From understanding the authentication flow to using the different APIs, and handling webhooks. 

When you build a Shopify app, you can choose either if you want your app to be an embedded app or not. 

- **Embeddable app** - You can let merchants access and interact with your app within the Shopify admin or Shopify POS.

- **Non-embeddable apps**- Merchants install your app and are then redirected outside of Shopify to your app's hosted page.

If you decided to build an **embedded app** for Shopify you’ll need to make sure that your app is secured and meets Shopify’s security policies and standards.

One of the most common content security policy Shopify requires you to handle is the [frame-ancestors](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors) - which tells the browser to allow or disallow the resources getting embedded using iframe, frame, object, embed, and applet element.

I saw that many developers are facing issues with embedded apps and looking for solutions in different communities, so I decided to write about it.

Here are the top 4 `frame-ancestors` errors on Shopify embedded apps and how to fix them.

## 1. The 'content-security-policy' header should set frame-ancestors https://[shop].myshopify.com https://admin.shopify.com, where [shop] is the shop domain the app is embedded on.

When you submit an app to Shopify, it might be rejected even before it got to the review team. Shopify has a set of automations that run when you submit your app. If you got the above rejection, it means that you haven't set the `Content-Security-Policy: frame-ancestors <source>;` header.

In order to fix it you'll need to add the `frame-ancestors` header on the response of the frame's request:

```typescript
// A middleware for setting up the header
router.use((req, res) => {
  const shop = req.query.shop;
  if (shop) {
    // Set the `frame-ancestors` header on the response
    res.setHeader(
      'Content-Security-Policy',
      `frame-ancestors https://${shop} https://admin.shopify.com;`
    );
  }
});

router.get('/app', (req, res) => {
  // Send a file, or redirect to your app's frame
  res.sendFile(path.join(__dirname, '../public', 'index.html'));
});
```

**Please note**: the `frame-ancestors` header must be different for each shop.

## 2. Refused to frame '{URL}' because it violates the following Content Security Policy directive: "child-src 'self' https://* shopify-pos://*". Note that 'frame-src' was not explicitly set, so 'child-src' is used as a fallback.

This error might be caused because you're trying to load a non-secured URL. Make sure that the App URL you're using is under the `HTTPS` protocol and not `HTTP`.

If you're running locally (`localhost`) you might want to use a tunnel service such as `ngrok` or `localtunnel`.

## 3. On Safari: [Error] Refused to load https://XXXX.myshopify.com/admin/auth/login because it does not appear in the frame-ancestors directive of the Content Security Policy.

Safari is a more strict browser than Chrome or Firefox, this error might appear if you're trying to redirect the user to a different URL after the initial app URL was loaded.

To fix this issue you'll need to make the redirect from the client instead of the server. Here's how:

```typescript
function generateRedirectHtml(redirectUrl) {
  return `
  <!DOCTYPE html>
  <html>
    <head>
      <title>Redirecting, please wait...</title>
      <script>
        setTimeout(()=>{
          window.top.location="${redirectUrl}";
        }, 3000);
      </script>
    </head>
    <body>
        Redirecting...
    </body>
  </html>
  `;
}

router.use((req, res) => {
  const redirectUrl = 'YOUR_REDIRECT_URL';
  const userAgent = req.headers['user-agent']
    ? req.headers['user-agent'].toLowerCase()
    : '';

  // For Safari, use client redirect
  if (
    userAgent.includes('shopify') ||
    (userAgent.includes('safari') &&
      !userAgent.includes('chrome') &&
      !userAgent.includes('firefox'))
  ) {
    res.send(generateRedirectHtml(redirectUrl));
    return;
  }

  // For other browsers, use redirect header
  res.redirect(redirectUrl);
});
```

## 4. Refused to display '{URL}' in a frame because it set 'X-Frame-Options' to 'deny'.

This error might appear when you're trying to redirect a user to your app's checkout page, or on the first time your app loads. 

The first thing you need to do is to make sure that the App URL is being configured correctly for the app.

![App URL field on Shopify](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4bfaosfwyzy8zno8pw0x.png)
 
Next, if you're trying to make the redirect from the server, try instead to make a request to the server to get the redirect URL, and make the actual redirect from the client. For example

```tsx
async function getRedirectUrl() {
  const result = await (await fetch('/getRedirectUrl')).text();
  window.location.href = result;
}

<button onClick={getRedirectUrl}>Click Me</button>
```

I hope this post will help you not to spend more time on fixing `frame-ancestors` errors on Shopify. If not, or if you have any other errors that you've seen, please leave a comment and I'll update the blog post.

In addition, if you're not familiar with the [Common Ninja Developer Platform](https://www.commoninja.com), that might be a good time to mention that you can spend less time integrating with Shopify and handling these kinds of errors by using our platform 🙂

## ❓ What is Common Ninja?
[Common Ninja](https://www.commoninja.com) is a platform that helps developers to build & monetize apps for e-commerce platforms very easily. With our single e-commerce API you can build your app once, and integrate it with multiple platforms like Shopify, BigCommerce, Wix, WooCommerce, and more. There's no need to rebuild the app and make it work on every platform individually.

In addition, Common Ninja offers a set of APIs and tools that help developers to boost up the development process, and provide payments and storage solutions.

## 📚 Learn more
If you're not familiar with Common Ninja or how to build your first app using our API, here's the previous article we wrote about it.

In addition, you can use the following resources to learn more and get help:

📜 [Common Ninja Docs](https://docs.commoninja.com)
💬 [Discord Community](https://discord.com/invite/cxqUTbvMNd)