How to implement an inline styles Content Security Policy with Angular and Nginx
Tags: angular, nginx, security
Published: 2021-06-13 09:16:40


## Intro

We want to make our applications as safe as possible, so we implement a content security policy(CSP) to mitigate Cross Site Scripting (XSS) attacks or Click Jacking.

The demo application contains an ngx-bootstrap toggle and a Angular Material slider component.

`application`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uo2l7a0fjb71lrtyjvxi.png)

## Implement the Content Security Policy(CSP)

Let's implement a CSP header. More information about [CSP](https://content-security-policy.com/).

```html
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
```

The result is that our application doesn't look the same anymore.

`application`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uudv8r6fwkhjlhy09lb1.PNG)

## What happened?

Due to our CSP policy, the browser blocks all inline styling that comes from an untrusted source.

`console.log`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5csp5mye237cnavn5dt1.png)

Angular Material and ngx-bootstrap styles are added with the styleUrls property. Angular will parse the component's styling and add them to the <HEAD> of the page. Based on the ViewEncapsulation property, it's global(none) or scoped(emulated).

`<style>`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5quq4s97yg3io6bpr754.PNG)

## How to solve

When we have control of our styling, we could place all our CSS into a separate file. Issue solved! However, we don't have control over how libraries handle their styling.

## Nonce approach

> Allows an inline script or CSS to execute if the script (e.g.: `<style nonce=" r@nd0m">`) tag contains a nonce attribute matching the nonce specified in the CSP header. The nonce should be a secure random string and should not be reused.

Let's add a nonce to our CPS policy and style tags, so our inline styling comes from a trusted source.

```html
 <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'nonce-random-csp-nonce';">
```

## Add nonce to style tag

When looking into the angular/platform-browser package, the following code is responsible for injecting the style tags.

`shared_styles_host.ts`

```javascript
private _addStylesToHost(styles: Set<string>, host: Node, styleNodes: Node[]): void {
    styles.forEach((style: string) => {
      const styleEl = this._doc.createElement('style');
      styleEl.textContent = style;
      styleNodes.push(host.appendChild(styleEl));
    });
  }
```

Luckily Angular provides us with dependency providers, which allows us to create a custom _addStylesToHost function.

We copy the `shared_styles_host.ts` and modify the `_addStylesToHost` method.

```javascript
 private _addStylesToHost(
    styles: Set<string>,
    host: Node,
    styleNodes: Node[]
  ): void {
    styles.forEach((style: string) => {
      const styleEl = this._doc.createElement('style');
      styleEl.textContent = style;
      styleEl.setAttribute('nonce', 'random-csp-nonce'); // Add nonce
      styleNodes.push(host.appendChild(styleEl));
    });
  }
```

We create a module that can be imported in our `app.module.ts`

`inline-styles-csp.module.ts`

```javascript
import { NgModule } from '@angular/core';
import { CustomDomSharedStylesHost } from './shared_styles_host';
import { ɵDomSharedStylesHost } from '@angular/platform-browser';

@NgModule({
  providers: [
    { provide: ɵDomSharedStylesHost, useClass: CustomDomSharedStylesHost },
  ],
})
export class InlineStylesCSPModule {}

```

After applying these changes, the style tag contains a nonce.

`styletag with nonce`

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rz2c11zznff6t0xz2l6t.png)
 

We now have a static nonce that is not secure.

>  The nonce should be a secure random string and should not be reused.

## Create a secure random string with Nginx

We use the `sub_filter` module of Nginx to replace the static with a dynamic string. In that case, we use the Nginx `$request_id` variable.

`nginx.conf`
```
sub_filter_once off;
sub_filter random-csp-nonce $request_id;

add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'nonce-$request_id'";
```

Also, note we add the `add_header` to our config file. 

Still, our solution doesn't work because Nginx replaces `random-csp-nonce` on the index.html file. Angular adds the style tags to the document after Nginx serves the document. When we place a hard-coded `<style nonce="random-csp-nonce" />` in the index.html it gets replaced with a dynamic nonce.

### Add metatag

We add a new metatag to the index.html so our script can look up the dynamic nonce value.

`index.html`

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="CSP-NONCE" content="random-csp-nonce"/>
</head>
<body>
  <app-root></app-root>
</body>
</html>
```

Let's update our `_addStylesToHost` method to query the nonce.

```javascript
 private _addStylesToHost(
    styles: Set<string>,
    host: Node,
    styleNodes: Node[]
  ): void {
    const nonce = document
      .querySelector('meta[name="CSP-NONCE"]')
      ?.getAttribute('content');
    styles.forEach((style: string) => {
      const styleEl = this._doc.createElement('style');
      styleEl.textContent = style;
      styleEl.setAttribute('nonce', nonce); // Add nonce
      styleNodes.push(host.appendChild(styleEl));
    });
  }
``` 
Each time we reload the page, a new random nonce is generated and applied to all style tags.

`random nonce styletag`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/llvpy1qwk0cymmdgow2b.PNG)

Our application looks the same as from the beginning. But now, we have applied a CSP policy :).

`application`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5f1nlqfjffndtif5k9c3.png)

## Conclusion

Although we have proof of concept on fixing the inline styles issue, the final and more sustainable solution should come from the community and the Angular team. For now, we have to inject a custom DomSharedStylesHost class.

## Demo

The Github code contains a full version of the code. However, for demo purposes, some code is stripped out of the original code.

🌎 [Live demo](https://angular-csp.azurewebsites.net)
📝 [Github](https://github.com/ferdiesletering/angular-csp-nonce)

## Improvements

#### Nonce in style tags remains empty

The browser parses the inline styles, but the actual nonce remains empty. I don't know why this occurs. The response Content-Security-Policy header contains the correct nonce value.

#### Stronger nonce token

The `$request_id` is not a  cryptographically secure random token.  We could improve this with an [nginx module](https://github.com/openresty/set-misc-nginx-module#set_secure_random_alphanum) as [Scott Helme](https://scotthelme.co.uk/csp-nonce-support-in-nginx/) suggests.

#### 100% secure?

The fact we use [AOT](https://angular.io/guide/aot-compiler) compilation means all code is already compiled and can't be tampered with. So although the code looks for the CSP header and gets the nonce, any other script that gets executed could do the same.

Any feedback or thoughts are welcome.