# Angular Content-Security-Policy Complex Nonce: Google Tag Manager

- Link: https://www.agilicus.com/angular-content-security-policy-google-tagmanager/
- Published: 2021-02-07T17:13:00+00:00
- Author: Don Bowman

Web applications get exploited, leading to economic and reputation damage. Rich 
content is difficult to protect. Complex standards and complex tooling fight with
each other. New technologies like Angular Single Page Applications and externally-
driven analytics make it difficult to construct a valid Content-Security-Policy.
If you get this wrong, you get malware injected on your site, as I wrote about [here](https://www.agilicus.com/stop-injected-malware-on-your-sit/).
Today let’s learn how, if we must have an inline script, we can do so with content-
security-policy complex nonce

Setting up Google Analytics (via Google Tag Manager) is difficult to understand 
and achieve securely: you are running Javascript fetched from an inline script, 
how do you cause it to be trusted without destroying all trust? The answer: a **
nonce**. Yes, content-security-policy is complex without the nonce. Set the nonce(
to a unique value) on each page load, and use this to indicate what scripts your
page has requested.

I’ve [talked](https://www.agilicus.com/project/web-security-101/) earlier about 
the complexities of web security, about how hard it is to balance security and functionality.
One of the tools that Content-Security-Policy allows is the **Nonce**. The **Nonce**
must be set differently on each HTTP response, making it complex: it requires participation
of the server.

In an Angular project, we normally use **-aot** and **-subresource-integrity**, 
this sets a secure hash on each resource that we build and serve. However, anything
that is fetched externally (e.g. Google Analytics) is more challenging. The recommended
way of using Google Analytics is via Google Tag Manager. In turn, you must use a
Nonce with it (as shown [here](https://developers.google.com/tag-manager/web/csp)).
How can we set that Nonce to be unique, on each request, in an Angular SPA? Read
on!

Before we start adding the nonce to external scripts, lets ensure we have hash-based
subresource integrity enabled for all internal, compiled Typescript:

    ```has-text-color
    ng build --aot --subresource-integrity --outputHashing=all --prod=true
    ```

If we look at our index.html in the dist directory, we will now see it looks something
like:

    ```has-text-color
    ...
    <link 
     rel="stylesheet" 
     href="styles.00fa916e08ff7ba62ff7.css" 
     crossorigin="anonymous" 
     integrity="sha384-+tmFFHbEXmmdzqySTeVvrRc2gEDLUMX3aMSwQxwB0spQcDT4EM2T6uHhCQ840BVW"/>
    </head>

    <body>
    ...
    <script 
     src="runtime.7b63b9fd40098a2e8207.js" 
     crossorigin="anonymous" 
     defer 
     integrity="sha384-l94orWp9/B8nJYxIHArstzxjyopeYkvuFAGAhd+P3juqcmrB3UHmRx4r1Ib76hcW">
    </script>
    ...
    ```

Those ‘integrity=’ lines have a sha384 hash of the body of the file. You can read
[more](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)
about Subresource Integrity. These hashes were generated by Webpack as part of the
angular build, in essence, for each file, it performed:

    ```has-text-color
    openssl dgst -sha384 -binary XXXX.js | openssl base64 -A
    ```

and this means your browser won’t load the resulting file if it has been modified.
But, that is not what we are here for today, we are here for external resources (
e.g. Google Tag Manager), and a safe way to allow them to be loaded, the nonce.

Next, we enable **[indexTransform](https://github.com/just-jeb/angular-builders/blob/master/packages/custom-webpack/README.md)**.
We cause it to, on production builds, add a script to the index.html HEAD section.
We add a magic string CSP_NONCE which we will then replace in the server side (using
lua in nginx).

    ```has-text-color
    npm i -D @angular-builders/custom-webpack
    ```

    ```has-text-color
    /*
     * index-html.transform.ts
     * This exists to modify index.html, after build, for prod,
     * to insert the google tag manager
     */
    import { TargetOptions } from '@angular-builders/custom-webpack';
    import { environment } from './src/environments/environment.prod';

    export default (targetOptions: TargetOptions, indexHtml: string) => {
      let insertTag = `<!-- No script for gtm, non-prod -->`;
      if (targetOptions.configuration || 'production') {
        insertTag = `<script nonce=CSP_NONCE src=https://www.googletagmanager.com/gtm.js?id=${environment.gtmTag} async></script>`;
      }

      const i = indexHtml.indexOf('</head>');
      return `${indexHtml.slice(0, i)}
                ${insertTag}
                ${indexHtml.slice(i)}`;
    };
    ```

    ```has-text-color
    // src/environments/environment.prod.ts
    export const environment = {
    ...
      gtmTag: 'GTM-XXXXX',
    ...
    };
    ```

    ```has-text-color
    // angular.json
    {
    ...
          "architect": {
            "build": {
              "builder": "@angular-builders/custom-webpack:browser",
              "options": {
                "aot": true,
                "outputPath": "dist",
                "index": "src/index.html",
                "indexTransform": "./index-html.transform.ts",
    ...
            "serve": {
              "builder": "@angular-builders/custom-webpack:dev-server",
    ```

OK, no we are generating an Angular SPA with a header which loads our Google Tag
Manager, with our tag, and a **Nonce** just waiting to be set. Note: you don’t have
to use the Webpack transform, you can just hard code in the script line with the**
<script nonce=CSP_NONCE** prefix if you prefer.

We will now replace the string nonce=CSP_NONCE with a new, per transaction value,
in our _nginx.conf_, adding a new location /index.html which will find all CSP_NONCE
in scripts, and alter:

    ```has-text-color
    location /index.html {

        set_by_lua_block $cspNonce  {
            local base64 = require('ngx.base64')
            local file = assert(io.open('/dev/urandom', 'rb'))
            local bytes = file:read(32)
            file:close()
            return base64.encode_base64url(bytes)
        }

        sub_filter_once off;
        sub_filter_types text/html;
        sub_filter_last_modified on;
        sub_filter '<script nonce=CSP_NONCE' '<script nonce="$cspNonce"';

        try_files $uri $uri/ /index.html;
    }
    ```

Now, when we fetch our index.html, the string is replaced on each transaction.

The net affect of this is we can safely use Google Tag Manager, since it, and its
chain of dependants, could only have been fetched via our code. If we have a flaw
in our application, perhaps incorrectly sanitising user-generated content, it will
be unable to fetch a script (since it cannot guess our **Nonce**). Give it a try!
No more ‘*’ and ‘unsafe-*’ for Content-Security-Policy, there’s no need.

Make sure you have a Content-Security-Policy header set (you can use nginx add_header
to set it) to a strong policy.

Now, lets test. My favourite tool is the [Mozilla Observatory](https://observatory.mozilla.org/).
Enter your URL, and let it scan. It will come back with a very actionable list for
you. Below is an example output showing the results after we have added content-
security-policy complex nonce.

[⌊Angular Content Security Policy (CSP) for Google Tag Manager (GTM) - Agilicus.
com: Secure your Angular application by implementing a strict CSP that allows Google
Tag Manager to function correctly. This includes configuring script-src, img-src,
and connect-src directives to whitelist Google Tag Manager and Google Analytics 
domains, preventing security vulnerabilities like cross-site scripting (XSS) attacks.
Agilicus provides solutions for robust web application security.⌉⌊Angular Content
Security Policy (CSP) for Google Tag Manager (GTM) - Agilicus.com: Secure your Angular
application by implementing a strict CSP that allows Google Tag Manager to function
correctly. This includes configuring script-src, img-src, and connect-src directives
to whitelist Google Tag Manager and Google Analytics domains, preventing security
vulnerabilities like cross-site scripting (XSS) attacks. Agilicus provides solutions
for robust web application security.⌉[
