Tutorial: How to Securely Embed / Display Javascript in Drupal 9

Let’s face it, Javascript is a critical part of today’s web experience. And if we’re being honest, that can cause a lot of pain (and the jokes flow freely). For instance, from just a few days ago:

Keeping in mind my recent post about HTML Security and Drupal 9, you absolutely can (and should) integrate Javascript into your Drupal applications. It’s going to provide a much more modern (and hopefully) user friendly experience to your users. BUT the answer isn’t to just open up your WYSIWYG editor and text filter config to let anyone dump whatever the heck they want into your pages. Down that road lies pain, agony, and security vulnerabilities. This tutorial is about doing it the right way. And it will demonstrate how to do it in a way that is secure!

We will use the Drupal 9 plugin system to write a basic block that can be placed on any page on a site via the Block Administration page, embedding Javascript without having to open up the text filters in the WYSIWYG to allow for script tags. Let’s get into it.

Why is it Dangerous to Script Directly in the WYSIWYG

Javascript lets you do some incredible stuff on webpages. But, as Uncle Ben famously said, with great power comes great responsibility. In almost all cases when a browser loads a page the JS on that page is executed wether the visitor explicitly wants it to be or not. While that script is executing, it can rewrite the contents of a page. It can change the styling of a page. It can capture and send data. Really, it can do just about anything it wants!

You might be thinking to yourself… I wouldn’t do that to my users. I write secure JS! I’m sure you do, but with respect, this isn’t necessarily an issue with the JS that YOU write. When you start embedding JS directly onto webpages without limitation, you expose yourself the the possibility of “accidentally” putting someone else’s code on your site. Once it’s there, you may not even realize that there’s something running in the background. So this isn’t so much a red flag to stop you from writing bad Javascript, it’s a red flag to make sure that you aren’t running someone else’s potentially malicious Javascript.

One of the most common examples of this type of vulnerability is Cross Site Scripting.

Cross Site Scripting (XSS): XSS attacks enable attackers to inject client-side scripts into web pages viewed by other users. A cross-site scripting vulnerability may be used by attackers to bypass access controls such as the same-origin policy. Cross-site scripting carried out on websites accounted for roughly 84% of all security vulnerabilities documented by Symantec up until 2007.

https://en.wikipedia.org/wiki/Cross-site_scripting.

How do you Prevent It

The WYSIWYG in Drupal is a prime example of “just because you can doesn’t mean you should.” I strongly strongly urge my projects and customers to limit the WYSIWYG to formatting tasks only. This helps to minimize the impact to the security of the site, as well as the accessibility and preserving the end-user experience.

This begs the question though… if you can’t run scripts directly in your WYSIWG, how do you?

We had a recent use case on the GovCon site that needed exactly this. We are using Sessionize this year to take our session submissions (it’s the same system that Drupal Con adopted). There is a Javascript snippet that you dump on a page to display all your submissions. it’s pretty slick!

In this case, I wrote a block. It’s a pretty minimal block at that, it’s 25 lines of code (most of which are not even really functional code).

The block is defined:

<?php

namespace Drupal\capitalcamp_blocks\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides a DGC ASessionize Block.
 *
 * @Block(
 *   id = "dgc_sessionize",
 *   admin_label = @Translation("DGC Sessionize Embed")
 * )
 */
class DgcSessionize extends BlockBase {
  /**
   * {@inheritdoc}
   */
  public function build() {
    return [
      "#theme" => 'dgc_sessionize',
    ];
  }
}

A basic theme function provides a template:

<?php

/**
 * @file
 * Contains block module functions.
 */

/**
 * Implements hook_theme().
 */
function capitalcamp_blocks_theme($existing, $type, $theme, $path) {
  return [
    'dgc_sessionize' => [
      'template' => 'dgc-sessionize',
    ],
  ];
}

And finally, I tossed the JS snipped directly into a Twig file.

<script type="text/javascript" src="https://sessionize.com/api/v2/kon992ln/view/Sessions"></script>

You can see the code in the pull request here.

The end result can be seen on the Drupal GovCon site here.

Expanding on this Example

Now, this example isn’t configurable which makes it a bit limited. So it works really well for this one snippet of Javascript but if I had say, a hundred different javascript snippets that I wanted to embed, this method would be a real pain in the butt (because I don’t want to have to manage hundreds of custom blocks that are one offs). So, let’s be smart about this!

Step back and think about what is the script / embed / iframe code that you need to run on your site. Consider if you have one, a handful, or a lot of these and if the sources are consistent. You would use the exact same methodology as above, but you’d want to look into making the block configurable. At that point, when placing the block (via Layout Builder or the Block Administration page) you just configure the block and the template handles the necessary embeds / scripting.

In the Drupal world, Lightning did this with the remote video object media type. It’s pre-configured to work with YouTube or Vimeo. All you have to do is drop a URL from one of these sources into the block and it “just works.” You don’t have to put in any video / embed codes.

So, if you have a similar use case where you want to embed something, build out some functionality that allows your user to customize what is needed (frequently the URL, maybe some of the url parameters) but don’t let them monkey around with the actual javascript / embed / iframe tags. At the end of the day, your site is going to be safer and your users can still post what they need. That’s a win-win in my book.

In Conclusion

Content Management Systems like Drupal have a ton of power and flexibility. But, as with many powerful frameworks you can also make really silly architectural decisions that will ultimately hurt you. Javascript is a particularly easy thing to overlook. Hopefully though, this post and some of the resources help you identify potential security vulnerabilities before they become big problems for you and your site. I also hope you walk away from this seeing how truly easy it is to write a custom block to display the JS in question. It’s NOT hard. And it’s so so much safer.

Related Content