Content Security Policies with Angular
For existing projects my task was to improve the security and add CSP-Headers (Among other things). When I added the headers to the servers response I got a lot of errors and the website was not rendering correctly anymore, because a lot of elements have been blocked. With this guide I want to help you to solve these issues and make your app more secure.
Before you read
- I only tested this with a relatively new version of Angular (v18)
- The examples are using IIS and Sass
CSP Header
I added the following CSP headers to our webserver by making these changes in Web.Config file which we place in the directory of the Client application (Angular).
Web.Config
<configuration>
...
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Content-Security-Policy" value="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src https://*; child-src 'none'; object-src 'none'; worker-src 'none';" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
When I tried this first I got exceptions like Refused to load the script because it violates the following Content Security Policy directive and Refused to load the font... it violates the following Content Security Policy directive: "default-src 'self'". Note that 'font-src'...
This was especially due to the rules default-src 'self'; script-src 'self';.
Adjust your Angular app
After some research I found that mainly the external fonts are the problem. Therefor I removed the following lines from index.html
index.html
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
I create local versions of the two files above and saved each font file that it contained to the assets folder. Then I changed the paths to match the local paths.
These are the first lines of the font files as an example:
_roboto-font.scss
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(/assets/fonts/roboto/v32/KFOlCnqEu92Fr1MmSU5fCRc4EsA.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
...
_materialicons-font.scss
/* fallback */
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(/assets/fonts/materialicons/v142/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
Then I imported the scss files in my main styles.scss file:
styles.scss
@import "roboto-font";
@import "materialicons-font";
...
Finally I also had to change how Angular is creating some inline-style elements by adding "inlineCritical": false:
angular.json
{
...
"configurations": {
"production": {
...
"optimization": {
"styles": {
"inlineCritical": false
}
}
}
...
}
}
After deploying these changes to the server the error messages were gone, and all the elements loaded correctly, because they didn't violate any rule anymore.
Of course you also have to make sure not to use other externals scripts etc. which are not part of a basic Angular app setup.