What’s a nonce? It sounds like a creature in a Dr. Seuss book or maybe the lesser known de León brother. But in fact, it is one piece of an important security technique, and you should be aware of it. In this article, we will explore what a nonce is and how it can be used to make an Angular application more secure.
Content-Security-Policy (CSP)
To understand what a nonce is, we first need to take a step back and talk about Content-Security-Policy or CSP. A CSP is one mechanism used to mitigate Cross-Site-Scripting (XSS) attacks which allow attackers to trick the browser into executing malicious content.
A CSP limits the sources of content, so that the browser will only execute content from predefined sources or domains. To implement a CSP, the server adds a Content-Security-Policy response header that defines the acceptable sources. You can set values for default-src that apply to all content, or you can get more fine-grained and define sources for specific content. For example, script-src for scripts, style-src for styles, etc. It is common to set default-src ‘self’; which allows all content from the same domain as the site itself.
Setting a CSP also blocks all inline scripts and styles by default. Many applications, including Angular applications, need to execute inline scripts and styles to make the magic happen. Have you ever wondered how the encapsulated component styles actually work? Angular injects these at runtime and this functionality is blocked if you have a CSP.
The quick fix for this is to add the ‘unsafe-inline’ keyword like this:
script-src 'unsafe-inline'; style-src 'unsafe-inline';
This allows Angular to do what it needs to do. But of course, these settings are not limited to Angular scripts and styles only. These settings allow scripts and styles from any source, effectively negating the CSP for scripts and styles. Enter the nonce.
The Nonce
Nonce is a shortened form of “number used once”. It is a unique, ephemeral value that must be shared between the server and client. The server generates the value and applies it to the CSP like so (line breaks added for readability):
script-src 'nonce-0136435421e75d07b8128a50b805041d';
style-src 'nonce-0136435421e75d07b8128a50b805041d';
Then that same nonce value must be applied to all inline scripts/styles. For example:
<script nonce="0136435421e75d07b8128a50b805041d">
// …
</script>
Now any inline script or style that has this nonce value can execute, without having to add ‘unsafe-inline’ to the CSP.
How to Use a Nonce in Angular
If your Angular application has a CSP and you do not want to use ‘unsafe-inline’ (and you shouldn’t if you can help it), this is the minimum policy you must have according to Angular (line breaks added for readability):
default-src 'self';
style-src 'self' 'nonce-randomNonceGoesHere';
script-src 'self' 'nonce-randomNonceGoesHere';
Since Angular 16, we can add a nonce value to the application element using the ngCspNonce attribute like so:
<app ngCspNonce="randomNonceGoesHere"></app>
You also have the option to provide the nonce using the CSP_NONCE injection token, but this article will demonstrate the ngCspNonce attribute approach.
Adding the CSP and Nonce Locally
In the angular.json file, add this under architect.serve(line breaks added for readability):
"options": {
"headers": {
"Content-Security-Policy": "default-src 'self';
script-src 'self';
style-src 'self';"
}
},
Run the project and you should see some ugly error messages like these:
Error message after applying a CSP
Now add the ngCspNonce attribute to the app component:
<app ngCspNonce="randomNonceGoesHere"></app>
And change the previously added “Content-Security-Policy” in angular.json to this (line breaks added for readability):
"options": {
"headers": {
"Content-Security-Policy": "default-src 'self';
script-src 'self' 'nonce-randomNonceGoesHere';
style-src 'self' 'nonce-randomNonceGoesHere';"
}
},
Stop and run the application again and see that the errors are gone because the nonce (randomNonceGoesHere)on the scripts and styles match the nonce on the CSP. It’s ok that we’re just using randomNonceGoesHere and not replacing it because the CSP we added in angular.json only applies when using ng serve. This is just for testing locally.
Adding the CSP and Nonce in Deployed Environments
Now we are fixed up locally, but how will this work in a deployed environment? For the deployed application, we will have to do some work on the server to add the CSP response header.
I am going to show an example using Nginx. I will assume familiarity with Nginx. If you are not familiar with it, take a look at this post to see how to get an Angular application running locally with Nginx.
In a deployed environment, we need Nginx to substitute an acceptable nonce for randomNonceGoesHere, both in the CSP and in the index.html file where the app component lives. You’ll recall that this is where we added the ngCspNonce attribute.
I am going to show an example using the $request_id that Nginx generates for each request. Nginx says the $request_id is a “unique request identifier generated from 16 random bytes, in hexadecimal”. To my mind, this is an acceptable nonce value, but your security team should be consulted before implementing in Production to determine if you should also make it cryptographically secure. But I want to keep this example simple, so I won’t here.
In the nginx.conf file, add these lines in the server block (which are documented with comments):
# add CSP, using nonce-$request_id ($request_id is unique request identifiery generated from 16 random bytes, in hexadecimal)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$request_id'; style-src 'self' 'nonce-$request_id';";
# substitute as many values as are found (not just one)
sub_filter_once off;
# substitute randomNonceGoesHere in the index.html file (ngCspNonce="randomNonceGoesHere") with
# the $request_id so that it matches the nonce-$request_id in the CSP
sub_filter randomNonceGoesHere $request_id;
Now for each request, a Content-Security-Policy header will be added with a unique nonce value for scripts and styles, and the same nonce value will be passed to Angular via the ngCspNonce attribute, and all will be well!
Content-Security-Policy-Report-Only
One last thing to mention is the Content-Security-Policy-Report-Only header. This is just like the Content-Security-Policy header, except that it only reports failures rather than actually blocking execution.
So, if you have a lot of work to do before you can eliminate ‘unsafe-inline’, you can set up the Content-Security-Policy-Report-Only header to not allow ‘unsafe-inline’ and see what would break if you were to turn it on for real. This could be helpful if you want to eliminate ‘unsafe-inline’ but can’t do it in one fell swoop.
That’s it! I hope you found this useful.
Bibliography
- Angular — Security
- unsafe-inline ⟶ CSP Guide (content-security-policy.com)
- Content Security Policy (CSP) — HTTP | MDN (mozilla.org)
- nonce — HTML: HyperText Markup Language | MDN (mozilla.org)
- Content-Security-Policy-Report-Only — HTTP | MDN (mozilla.org)
- http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_id
- Module ngx_http_headers_module (nginx.org)
- Module ngx_http_sub_module (nginx.org)
Top comments (0)