You are a developer: You were very happy about the successful defense against XSS attacks. The iframe
sandbox and the Content Security Policies (CSPs) presented in the last article have done an excellent job.
But the digitalization strategy of your favorite online store is moving forward: To further optimize loading times and reduce hosting costs, you decide to outsource all static assets – including the compiled JavaScript bundles and CSS files of your Angular application – to an external cloud provider.
This sounds like a good idea, until you realize that this will shake up your existing CSP. Your existing CSP limits the sources of content so that the browser only executes content from predefined sources or domains. By default, a CSP also blocks all inline scripts and styles.
And this is exactly the catch with Angular applications: Have you ever wondered how the encapsulated component styles actually work? Angular injects these styles at runtime as <style>
-tags directly into the <head>
of your HTML document. The loading of the styles is suddenly prevented by your previous configuration and the website does not look as it should. A “quick fix” solution saves the day: allow style-src: 'unsafe-inline'
in your CSP.
As an attacker:in: This move of assets has not gone unnoticed by you. You are rubbing your hands together. You know full well that Angular relies on inline<style>
tags for its styles and that many developers have to allow unsafe-inline
in the CSP. Although this allows Angular to inject its styles, it also allows anyone (including an attacker) to place inline styles. The style-src
rule then no longer only applies to Angular, but to all sources. Now that the assets are coming from an external service and the URLs are becoming even more unpredictable, this is the perfect vulnerability. You sharpen your blades, ready to tap into user data via manipulated style properties or CSS expressions or to distort the presentation of the page – because the unsafe-inline
gives you the freedom to do so.
The rescue: CSP with style nonces in Angular 16!
You’re a developer: Luckily, you’ve heard that Angular 16 comes with a long-awaited feature: support for nonces in inline styles! This is the solution you’ve been waiting for. You can finally banish unsafe-inline
from your CSP and still retain the functionality of your Angular styles.
A nonce (number used once) is a random value that is generated by the server for each request. This value is supplied both in the HTTP header of the CSP (or in the <meta>
tag) and in the respective <style>
tag. The principle is simple but ingenious: only <style>
-tags, whose nonce
-attribute matches the value sent in the CSP header may be used by the browser.
How does this work for your client-side Angular app?
As Angular is a single-page application (SPA), a index.html
file is initially delivered by the server. This index.html
is the central point for the nonce implementation, as your web server must dynamically insert the nonce value here.
- Changes in the
index.html
of your Angular application:
You have to adjust two things in yourindex.html
. Remember that these adjustments contain placeholders that will later be replaced by your web server:
<!doctype html>
<html lang="en">
<head>
<title>Online Shop</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 'self'; style-src 'self' 'nonce-random_nonce_value'; script-src 'self' 'unsafe-eval';">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root ngCspNonce="random_nonce_value"></app-root>
</body>
</html>
- CSP definition: The CSP itself is still defined here in the
<meta>
tag. Note that we changestyle-src
from'unsafe-inline'
to'self' 'nonce-random_nonce_value'
. The stringrandom_nonce_value
is a placeholder here. ngCspNonce
Attribute: You add the new attributengCspNonce
to the root element of your Angular application (<app-root>
) and give it the same placeholder value as in the CSP. Angular 16 uses this attribute to know which nonce value it should insert into the dynamically generated<style>
tags.
- Changes in the Nginx configuration (your web server):
Your Nginx web server must now do two things for every request fromindex.html
:
a. Generate a new, unique nonce value.
b. Insert this nonce value both in theContent-Security-Policy
header and in the places where the placeholderrandom_nonce_value
is located inindex.html
.
We use thesub_filter
module from Nginx for this. This is the server-side part that is necessary to handle the nonce values dynamically.
server {
listen 80;
server_name your-angular-shop.com;
# Allows replacing multiple occurrences of the placeholder
sub_filter_once off;
# Replaces 'random_nonce_value' with the Nginx $request_id (a unique value per request) sub_filter random_nonce_value $request_id;
# Sets the CSP header and uses $request_id as the nonce value
add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'nonce-$request_id'; script-src 'self' 'unsafe-eval';";location / {
root /path/to/your/angular/dist; # Path to your Angular build files
index index.html;
try_files $uri $uri/ /index.html;
# IMPORTANT: Filtering must be enabled here
# so that substitution can take place in the index.html.
sub_filter_types text/html;}
}
What is happening here?
sub_filter_once off;
: Ensures that Nginx replaces all occurrences of the placeholder (hererandom_nonce_value
) in theindex.html
and not just the first one.sub_filter random_nonce_value $request_id;
This is the centerpiece. Nginx replaces the placeholderrandom_nonce_value
with the internal$request_id
. This$request_id
is a value that is generated by Nginx for each request and is therefore unique.add_header Content-Security-Policy "..."
Nginx adds this header to the HTTP response. Note that the$request_id
is also used here as a nonce value instyle-src
.sub_filter_types text/html;
: Ensures that thesub_filter
module is only applied to HTML files.
Why does it save the day?
Your Angular 16 frontend now benefits from a much stricter CSP:
- None
unsafe-inline
more for styles: Since Angular now inserts the dynamically generated nonce value into its<style>
tags, you can removeunsafe-inline
from yourstyle-src
directive. This closes a big attack surface! Moving the static assets to the cloud provider no longer requires extensive exceptions in your CSP, as validation is now done via the nonce instead of fixed domain lists. - Protection against injections: Attackers cannot predict the random
$request_id
(your nonce value). Even if they succeed in injecting malicious CSS code or<style>
tags into the website, this will not be applied unless it has the correct, current nonce value. As the attacker does not know this value and cannot predict it, the attack fails.
Important notes and restrictions:
$request_id
is not cryptographically secure: while$request_id
is unique per request, it is not designed as a cryptographically secure nonce value. For very high security requirements, a stronger, random value generated by a backend service, which is then injected into theindex.html
via Nginx, might be better. However, for many use cases,$request_id
is a practical and effective improvement.- Other dynamic
<style>
tags: This solution covers the<style>
tags generated by Angular itself. If you use other libraries or frameworks that also dynamically add<style>
tags, you must check whether they also support anonce
attribute or whether you need to take other measures. - Long-lasting sessions: If users have your Angular application open for very long periods of time (without reloading the
index.html
), the nonce value once set remains active. This is typically not a major problem, as XSS attacks usually aim to exploit a vulnerability during the initial loading time or during navigation.
Stay tuned!
Your online store is a little more secure again! Attackers now have an even harder time infiltrating malicious code, even if assets are loaded from external cloud providers. With Angular 16 and a clever Nginx configuration, you are ideally equipped for the challenges of modern web security. The next challenge is sure to come!
Write us an email – we look forward to hearing from you! hello@qualityminds.de or on LinkedIn