Using Google Tag Manager with a Content-Security-Policy
Tags: security, csp, webdev, tutorial
Published: 2020-11-30 23:20:45


Configuring [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP) and allowing Google Tag Manager (GTM) scripts can be split into two main parts:

1. Setting GTMs standard tag types.
2. Setting GTMs **Custom HTML** tag types.

The first part will be covered in short notes to provide a handy overview. However, the main concern of this article is the second part, as it is a bit more tricky to set.
Also, note that using `unsafe-inline` defeats the whole purpose of CSP, so that's not an option.

## 0) Nonce

The only practical approach for CSP-allowing is to use the unique server-generated `nonce` value, created either via an appropriate library or simply generating the proper random string. The same `nonce` value can be used for all scripts, but it must be uniquely generated for each client. For example, generating it in javascript could look like this:

```js
const GENERATED_NONCE = crypto.randomBytes(16).toString("base64");
```

Add the rule to CSP header and allow generated `nonce`:

```html
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-{GENERATED_NONCE}'" />
```

## 1) Allowing GTM and it's standard tag types

This part is fairly simple and nicely documented in [developers.google.com](https://developers.google.com/tag-manager/web/csp).

Outlined main steps are:

1. Whitelist `nonce` in the CSP header (already done in the previous section of this article).
2. Use **nonce-aware** version of GTM snippet - it will propagate the `nonce` to its scripts.
3. Whitelist necessary resources in the CSP header for the tags used (just follow errors in the console).

In some sense, this should be the end of this article, but unfortunately, GTM doesn't propagate the `nonce` to any **Custom HTML** tags.

## 2) Setting Custom HTML tag types

### Getting the nonce variable in GTM

In order to add the `nonce` attribute to the Custom HTML scripts, it must be first defined as a GTM variable:

1. Add `id="gtmScript"` to the nonce-aware version of GTM snippet - this will be used to target the element and capture `nonce`.

```html
<script id="gtmScript" nonce="{GENERATED_NONCE}">
  // GTM function
</script>
```

2. In GTM, create a new variable that will capture the nonce.
   Use **DOM Element** type, and select the ID of the GTM snippet (`gtmScript` in this guide).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/sv5cbkm6ieoh4yocuu1u.png "GTM Variable configuration for nonce")

### Allowing custom HTML script

Now that the `nonce` variable is available in the GTM, add it to the Custom HTML script.

```html
<script nonce="{{nonce}}">
  console.log("CSP-allowed script with nonce:", "{{nonce}}");
</script>
```

If the tag is not firing, check the **Support document.write**. This can be a key step in Single Page Applications.
The GTM Custom HTML script is now nonce-allowed and fires as expected.
Of course, any assets used by this script will now need to be allowed in the CSP header.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xj164oyi4o13par4mas6.png "GTM Custom HTML configuration")

### Script within a script

Many tracking scripts are creating and firing additional script within themselves.
These will also be blocked as inline-scripts.
Find out where and how they are created, and add `nonce` to them as well.

Usually, the code looks similar to this:

```js
var script = document.createElement("script");
script.type = "text/javascript";
script.async = true;
script.src = "https://tracking.js";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(script, s);
```

Edit this part of the code and insert the `nonce` variable, in the same manner along with other attributes.

```js
script.nonce = "{{nonce}}";
```

Again, pay attention and whitelist any necessary assets that are now being blocked from this newly allowed script.

That's it - Custom HTML script is now fully CSP-allowed.