DEV Community

0xdbe
0xdbe

Posted on • Updated on • Originally published at 0xdbe.github.io

Angular Security - Disable Inline Critical CSS

Improving load time is crucial for the success of your application. One way to reduce this load time is to optimize the CSS loading but it is quite tricky, because CSS files are render-blocking. This means that the browser must download and parse these files before starting to render the web page.

That's why Angular provides CSS optimization in order to reduce this render-blocking delay, and at the same time, to improve the First Contentful Paint (FCP). This optimization involves first inlining critical CSS and delaying the loading of non-critical CSS.

This article describes what's wrong with this optimization and how to disable it to keep a strict CSP (Content Security Policy).

What's wrong?

Inline Critical CSS is an optimization that impacts our CSP (Content Security Policy):

style-src-elem 'unsafe-inline';    // For Inlining critical CSS
script-src     'unsafe-inline';    // For Delaying non-critical CSS
Enter fullscreen mode Exit fullscreen mode

To understand why it is necessary, let's take a look at these practices.

Inlining critical CSS

During the build process, Angular extracts first all CSS resources that block the rendering. Once critical CSS are extracted, Angular inlines them directly in the index.html file. In order to authorize inline CSS, we have to add the following content in our CSP:

style-src-elem 'unsafe-inline';
Enter fullscreen mode Exit fullscreen mode

With this configuration, our CSP is not able to block CSS injections anymore. This problem is not new since inline CSS is used by Angular for Component Style. So, inlining critical CSS should not further affect our CSP.

Delaying non-critical CSS

After inlining critical CSS, the rest can be postponed. However, HTML and CSS don't support asynchronous loading for CSS files. To circumvent this issue, there is an Angular trick to load non-critical CSS asynchronously using media attribute:

<link rel="stylesheet"
  href="styles.1d6c8a3b8017c43eaeda.css"
  media="print"
  onload="this.media='all'">
Enter fullscreen mode Exit fullscreen mode

Media type (print) doesn’t match the current environment, so the browser decides that it is less important and loads the stylesheet asynchronously, without delaying the page rendering. On load, we change media type so that the stylesheet gets applied to screens. In order to authorize event handlers that run inline script, we have to include the following content in our CSP:

script-src 'unsafe-inline';
Enter fullscreen mode Exit fullscreen mode

This configuration almost defeats the purpose of CSP, thus we could be exposed to XSS attacks.

How to fix this?

For security purposes, inline critical CSS must be disabled to keep a strict CSP.

Inline critical CSS is a new optimization introduced in Angular 11.1. However it was disabled by default. This optimization is now enabled by default in v12 and you have to set inlineCritical to false in angular.json for each configuration:

{
  "configurations": {
    "production": {
      "optimization": {
        "scripts": true,
        "styles": {
          "minify": true,
          "inlineCritical": false
        },
        "fonts": false
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

With this configuration, Angular includes CSS as before:

<link rel="stylesheet" href="styles.css">
Enter fullscreen mode Exit fullscreen mode

And we don't have to weaken our CSP!

Latest comments (2)

Collapse
 
chan_austria777 profile image
chan 🤖

Thanks for the wonderful article. My question is how do you deal with improving FCP and fixing FOUC while keeping CSP configurations secure?

Collapse
 
0xdbe profile image
0xdbe

For my small app (65000 loc), disabling inline critical CSS increases FCP of 0.2 second.
It's not a big deal in my case because it was an internal application for employees.
This is a trade off between security and performance.

Personally, I’m waiting for a better way to load CSS asynchronously.
script tag has async or defer attributes, it could be the same for CSS but not.