Allow Embedding of Content in Cross Origin iframes

So that I could get my business site up with a contact form quickly I decided to use a contact form from a remote site and place it in an iframe. This has worked out particularly well even though I did need to learn about Content-Security-Policy and X-Frame-Options to make this happen.

First, let's have a quick look at the set-up

The set-up is nothing special, it is an iframe on with the src set to this site I have stripped down the template to remove navigation and unneeded elements. To read about how that is done in Drupal you can read about it in the Using Template Suggestions and Twig to Alter the Page Output in Drupal 9 article.

However, as with a lot of server stuff, this was a hassle. Even after years of working with iframes from other services I didn't know that you had to enable the parent site to allow a remote site to host a page in an iframe. I was presented with the message illustrated in the following image.   

firefox can't open page iframe cross and remote origin error message

At least Firefox gives you some hints as to what is wrong, something Chrome doesn't do. You can also check the developer tools for more information but you won't actually get an error response, it only shows you the headers that the server returns or doesn't return.

So what I thought was going to be an easy solution to allow quick set up of a form ended up being a real brain-teaser. Everything I tried just didn't work, I won't go into detail but a lot of resources and proposed solutions suggest that you use the X-Frame-Options HTTP response header but if you read the documentation you'll find that the ALLOW-FROM uri directive is obsolete.

With that out of the way, let's look at the solution.

The Solution

The solution that I came to understand is correct is to use Content-Security-Policy and the frame ancestor directive. I tried adding this to .htaccess as per my understanding but this didn't work.

Even though I like Drupal as a CMS solution due to its easy to use modular system; since Drupal 8 and 9 I have tried to keep the contributed modules limited to the absolute minimum so I left trying the Security kit to last. In the end, though I had to try it and this worked.

In the end, I used the Security kit module to implement this

Security Kit is a well-supported module that has 60,000 reported installs. However, using the module only to enable remote access to serving pages in iframes seems a little overkill. That said you may find that the multitude of other options "to mitigate risks of common web application vulnerabilities like Cross-site Scripting, Cross-site Request Forgery and Clickjacking" useful.

Using a system like Drupal allows for point and click enabling of a lot necessary functionality making your job easy. I also find that by using projects such as Security Kit you become aware of stuff you may not have been aware of. So if you do install Security Kit or any other module for that mater be sure to check it out fully. It may teach you something.

To install a module on Drupal I have written a quick guide, so if you are unfamiliar with the process visit that page and after you have installed the Security Kit come back here.

Once you have enabled the module you need to visit the setting page.

Manage > Configuration > Security Kit

There are only two settings you need to enable and those are:

  • Send HTTP response header: Send Content-Security-Policy HTTP response header with the list of Content Security Policy directives.
  • frame-ancestors: Specify trustworthy hosts which are allowed to embed this site's resources via <iframe>, <frame>, <object>, <embed> and <applet> elements.

Both these settings are found in the Content Security Policy accordion which is in the Cross-site Scripting section accordion. Once you have checked the setting and added your remote trustworthy host scroll to the bottom and click save configuration and walla, it works!

If for some reason it does work be sure to flush the caches.

This module does what was suggested, it enables frame-ancestors so it must have been where I was putting the rule in the .htaccess file that was the issue.

Add a Re-direct Page for better User Experience

One final thing is, if you don't set a redirect then the homepage will load. Since the homepage is the homepage of the host site it will be better to add a redirect page and create another template override. To do this follow the same steps in the template override and twig article and then in the contact form settings add the redirect URL.

contact form drupal redirect path input settings

Follow up

After going through the same issue with getting a cross-origin POST request working with Drupal and needing to add the directives to the server virtual host in the apache sites-available configuration file I decided to add the Content-Security-Policy to the virtual host too. In the directory section where I added the headers, I found that adding the Content-Security-Policy worked. So if you don't need any of the other provided security settings I would go with the VirtualHost solution as it means you can keep your Drupal site to only necessary modules making updating and future upgrading simpler. If you don't have server access then the module solution is good and well-supported. Check the linked article for the full section of code but following is the line you need to add.

Header always set Content-Security-Policy "frame-ancestors;"

Even though this solution works I will be replacing this solution shortly with a form. If you are interested in how to do that make sure you read the mini-series on using Vue.js  as a front-end and making POST requests to Drupal 9.

Thanks for taking the time to read and if you want to learn more about front-end web development using Drupal and other technologies such as Vue.js be sure to sign up for my newsletter.