We’ve recently come around to lifting the applications in my current project to Angular 16. One of the things the new version brings is support for nonces in inline styles.

As a short reminder, the styles your components define get included as <style> tags in the <head> of your application dynamically at runtime. This enables the style separation Angular provides (take a peek in your browser’s DevTools to see what I mean). Until now, this meant that – if you were using a Content Security Policy (CSP) – you had to allow unsafe-inline as a style-src, which potentially leaves you open to injections.

With the advent of Angular 16, you can now make the Angular runtime add a nonce attribute to each of the <style> Tags, so you can limit the CSP to style-src: 'self' 'nonce-$VALUE'; and have the browser reject each <style> that does not have that nonce value.

Preconditions

In production, our application gets built into the usual bunch of static HTML and JS files and then packaged into a Docker container that has nginx as the web server. We also provide our own configuration for nginx during the Docker build.

Both aspects are important for the setup I describe below; if your setup looks different, you will have to see what you have to adapt. Since the nonce is something that is added at runtime but (as the name nonce – number used once implies) needs to be unique for each time the HTML document of the application is served, we need to be able to process that HTML document each time.

Changes in the application

Your applications index.html looks roughly like this

<!doctype html>
<html lang="en">

  <head>
    <title>Application Title</title>
    <base href="/">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Security-Policy" content="default-src 'none';
    style-src 'self' 'unsafe-inline';">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
  </head>

  <body>
      <app-root></app-root>
  </body>

</html>

Note that in this example, we’ve also included the CSP as a <meta> Element to the document. This way it also gets picked up when you run the built-in development server, but of course you can also have your nginx add the CSP as an HTTP header.

To include the nonce, you have to do two things:

We’ll let nginx take care of replacing these and just set them to the same value, so our index.html becomes this:

<!doctype html>
<html lang="en">

  <head>
    <title>Application Title</title>
    <base href="/">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Security-Policy" content="default-src 'none';
    style-src 'self' 'nonce-random_nonce_value';">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
  </head>

  <body>
      <app-root ngCspNonce="random_nonce_value"></app-root>
  </body>

</html>

Changes in the nginx configuration

To have nginx make the necessary replacements, we’ll use the http_sub module. For it to do its work properly, we have to add two settings to our configuration. They could go into a location block, but they can also go within the server block and then apply globally.

# substitute 'random_nonce_value' by a unique value per request to allow ourselves
# a proper strict CSP without unsafe-inline
sub_filter_once off;
sub_filter random_nonce_value $request_id;

The things happening here are

Conclusion and Caveats

Put all this together, and your index.html will declare a new nonce every time the root document of the application is served. This value is picked up by Angular, so any style tag it creates conforms to the CSP and is used as before.

There are some things to keep in mind, though:

So while adding nonces for styles is not a silver bullet, I think it is a relatively easy way to improve on your existing CSP and secure your application a bit better.

Mehr zum Thema Web-Security? Wir bieten ein 3-Tages-Training an.