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
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:
- Add a
nonce-$VALUE
setting to thestyle-src
section of the CSP. - Add an
ngCspNonce
attribute to the root element of your application (this is where the Angular Runtime picks it up). You have to set the content of this attribute to the same$VALUE
you use in the CSP.
We’ll let nginx take care of replacing these and just set them to the same value, so our index.html
becomes this:
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.
The things happening here are
-
sub_filter_once off
makes nginx look for multiple occurrences of the strings to replace. Since we haverandom_nonce_value
twice in ourindex.html
we need that. - The
sub_filter
line declares which string needs to be replaced and by what.$request_id
is a value per request that nginx creates anyway, so it comes in handy here.
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:
- While
$request_id
is unique per request, it is not a cryptographically secure ID, so it might be possible to predict values. As usual for security topics, you need to decide for yourself if that is secure enough for your environment. - This setup covers the core Angular functionality. If you use additional frameworks that also add
<style>
tags dynamically, you might need additional configuration of even scripting to ensure that their styles still work. - Some of the value of the nonce comes from its one time use and its ephemerality. If your users have your application open for a long span of time, that might be counteracted.
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.
Learn more about web security. We have a 3-day-training.