How to set dynamic header for Content-Security-Policy: frame-ancestors

Hi, I’m using Netlify to host a Shopify app that is run inside an iframe in the Shopify dashboard. Shopify requires that I set the proper Content Security Policy frame-ancestors directive to avoid clickjacking attacks. The frame-ancestors header should be set dynamically to whatever shop origin has loaded the app. I read the _headers docs on Netlify and did some searching but couldn’t find anything if this is even possible. Others have accomplishes this by using middleware to set the CSP header unfortunately my app is a JavaScript SPA. Any help is greatly appreciated. Thanks!

Content-Security-Policy: frame-ancestors https://example.myshopify.com https://admin.shopify.com;
1 Like

Hey @philnetlify1,

Could you share the Netlify site URL?

Hi, @philnetlify1. To confirm, is this something you looking to solve programmatically because there are many shops being used so the subdomain cannot be known before hand?

If so, Edge Handlers would be a solution once they become publicly available. In the meantime, the only solution I see is to use a function to dynamically generate the headers.

Yes, you are correct. Basically I will need to programmatically validate the domain *.myshopify.com and then set the frame-ancestors to whichever shop domain is being used. The subdomain cannot be know before hand. I’ll look into functions. Thanks!

Hi @philnetlify1 Did you solve this? Can you share your functions file on how did you achieve this?
I have created a file in root-app-folder/.netlify/functions/index.js and this function never gets called.

Hi @srameshr

Functions are accessible via /.netlify/functions/<function-name> only after they are built.

The default directory for Netlify to automatically detect and build functions are per documentation is YOUR_BASE_DIRECTORY/netlify/functions.

Unless it is a function triggered on an event a function is never automatically called, you need to explicitly call it.

Can you please share your file on how you do this @philnetlify1

Even I am trying to set CSP for my embedded Shopify app

Hey @srameshr , I ended up switching over to Cloudflare Workers Sites. The following gist got me started configuring my web worker script. Some of it is probably applicable to Netlify function too. Hope it helps :slight_smile:

Just to clarify this is how I set my headers.

...
// return await getAssetFromKV(event, options);
const page = await getAssetFromKV(event, options);
const response = new Response(page.body, page);
response.headers.set("Content-Security-Policy", `frame-ancestors 'none'`);
const { searchParams } = new URL(event.request.url);
const shop = searchParams.get("shop");
if (shop !== null && shop.match(/^[a-zA-Z0-9][a-zA-Z0-9-]*\.myshopify\.com$/) !== null) {
  response.headers.set("Content-Security-Policy", `frame-ancestors https://${shop} https://admin.shopify.com`);
}

return response;

Yes, but how do we achieve this to set headers on Netlify functions? Functions wont be run on beforePageRequest right? They are just like API endpoints if you dont want to spin up your own API servers

Hey there folks! Just chiming in here to say we are working on a project that might make this possible in the future. I can’t share a timeline, but stay tuned!

As always, thanks for bringing great collaboration and great ideas to the Forums.

2 Likes

Did this ever get worked on? Might you be referring to the Edge Function feature currently in beta?

Indeed it did, and Edge Functions are the feature that Hillary was teasing :slight_smile:

These allow you to transform the input or output (for instance: edge-functions-examples/pages/set-response-header at main · netlify/edge-functions-examples · GitHub).

Got it. For anyone else that finds this, here’s the function I wrote that seemed to pass Shopify approval:

// Handler to check the request header for a shopify domain and if it is there, include it in the response headers frame-acnestor list
export default async (request, context) => {
  const response = await context.next();
  
  // Get rid of everything before the ? in the URL
  const bits = request.url.split("?");

  // If shopify domain is present, add it to the response headers frame-ancestor list
  if (bits.length >= 2) {
    // Get the "shop" param from the query string
    const queryString = new URLSearchParams(bits[1]);
    const shop = queryString.get("shop");

    // If domain ends with "myshopify.com", allow it
    if (shop.endsWith("myshopify.com")) {
      response.headers.set(
        "Content-Security-Policy",
        `frame-ancestors https://admin.shopify.com https://${shop}`
      );
    }
  }

  return response;
};

@fool The docs say to not use edge functions in production, but don’t go into any more detail on timeline or the risks. Can you enlighten us?

1 Like

We don’t have a firm timeline for GA. We and many other customers do already use them in production, but since they’re a beta feature, there could be instability or breaking changes to the API until we do release.

We’ll definitely make a lot of noise when they are GA-ready!