Skip to main content

oEmbed resources from a custom domain in Drupal

In 2018, Drupal implemented support for oEmbed in media and the ability to serve them from a different domain than the main one in order to add an extra layer of security against XSS (Cross-Site Scripting) attacks.

By default, these third-party resources are already displayed inside an iframe but under the same domain, which makes it possible for the oEmbed resource to access cookies from the main domain. This poses a security risk that the provider of the resource can exploit.

Configuring a custom domain for the oEmbed iframes is very simple. You just need to go to /admin/config/media/media-settings and set up a specific subdomain. This way, Drupal will start serving the oEmbed content from that subdomain, isolating it from the main domain and adding an extra layer of security.

However, this configuration does not only affect the oEmbed iframes but also makes the entire website accessible from that subdomain, which could cause SEO issues, as search engines might index duplicate content or confuse the authority of the main domain.

Therefore, it's important to restrict traffic from the oEmbed domain and configure the correct CSP (Content Security Policy) rules.

Requirements

How to restrict traffic for the oEmbed domain

To restrict traffic on the oEmbed domain, we'll use a rule in the .htaccess file to limit the queries that can access that URL. We'll use {HTTP_REFERER} to filter requests, establishing that if {HTTP_REFERER} is from the main domain, it's a valid request and the resource is shown.

<IfModule mod_rewrite.c>
  RewriteEngine on
  # Only request to oembed subdomains from allowed domains are allowed, 
  # otherwise a 403 is displayed.
  <If "%{HTTP_HOST} =~ m#oembed.(.*)# && %{HTTP_REFERER} =~ m#https?://(.*\.)*(ddev\.site|my-domain\.com).*#">
    Header set x-Drupal-Oembed "TRUE"
  </If>
  <ElseIf "%{HTTP_HOST} =~ m#oembed.(.*)#">
    RewriteRule .* - [F,L]
  </ElseIf>
</IfModule>

In the first rule, it checks that:

  1.  The request host is to the oEmbed domain, in this case, "oembed.my-domain.com"
  2. The referrer is not empty and matches one of the website’s domains:
    1. https://www.my-domain.com -> production
    2. https://my-domain.com -> production
    3. https://dev.my-domain.com -> development environment
    4. http://my-domain.ddev.site -> local environment

No further action is required if the above conditions are met; in the example, a header is set for debugging purposes.

In the second condition, a 403 error is returned, halting the execution of the .htaccess file. Instead of returning a 403, you could set up a redirect to the main domain, but you would need to consider the external cache layers (e.g., Varnish), as they could cache the redirects, causing unwanted effects.

CSP configuration

The CSP (Content Security Policy) module improves the security of a website by defining which content sources can be loaded and displayed.

For our implementation to be secure and function correctly, it is essential to properly configure this module.

URLs to consider

  • The site URL (https://www.my-domain) and its variants for each environment (https://www.dev.my-domain).
  • The "oembed" URL (https://oembed.my-domain) and its variants for each environment (https://dev.oembed.my-domain).
  • Lastly, the URL of the resource we want to embed, for example, YouTube (https://www.youtube.com).

We'll use the * wildcard in CSP to simplify the configuration of the URLs:

  • https://*.my-domain: refers to both the main URL and the various environments and oembed URLs.
  • https://*.youtube.com: we apply the same pattern, though in this case, it's not strictly necessary.

Directives 

There are three key CSP directives to focus on:

  • frame-src: Specifies trusted sources for <iframe> and <frame> elements.
  • frame-ancestors: Defines the trusted hosts allowed to embed site resources via <iframe>, <frame>, <object>, <embed>, and <applet>.
  • child-src: Specifies trusted sources for <iframe> and <frame> elements, as well as for loading Workers.

Directive configuration

The directives should be configured as follows:

  • frame-src: 'self' https://.my-domain.com https://.youtube.com
  • frame-ancestors: 'self' https://*.my-domain
  • child-src: 'self' https://*.youtube.com

Potential issues

Cache of redirects

As mentioned in the example, if you choose to set up a redirect, it's important to consider the external cache layer configuration. If someone tries to directly access the resource (e.g., https://oembed.my-domain.com/media/oembed?...) and doesn’t have the correct referrer, a 403 is returned along with cache headers. This response will be cached in the external cache layer, and in future requests, the external cache will return the 403 without reaching Drupal.

Iframe allow features

The "allow" tag in the iframe indicates the browser APIs the iframe content can access. It's mandatory to fill this out because by default, iframes serving content from a different origin don’t have API access. A subdomain is considered a different origin.

Another point to note is that even if API access is allowed, the domain must be a valid origin. By default, all (*) origins are allowed, but they may be restricted with the "Permissions-Policy" header.

Fran Rouco

Fran Rouco

Drupal developer
Photo Luis

Luis Ruiz

Senior Drupal Developer