skip to content

Under a nonce-based CSP, why do an Angular app's component styles need a nonce, and when do you use CSP_NONCE versus the ngCspNonce attribute?

level: middleimportance: should knowfreq 34%

answer

  1. styles inserted at runtime
  2. style-src without unsafe-inline
  3. root element attribute
  4. injection token read by renderers
  5. cacheable index.html

basics

~20 s

Angular inserts component styles as <style> elements at runtime, which a strict style-src blocks unless they carry the nonce. Use ngCspNonce when the server templates index.html per response, and CSP_NONCE when the nonce arrives at runtime and index.html stays cacheable.

solid answer

~40 s

Component styles are not in your stylesheet file: Angular's renderer adds them to the document as `<style>` elements when a component is first rendered, and with `ShadowDom` encapsulation it adds them inside each shadow root. A strict `style-src 'self' 'nonce-…'` rejects those inline styles unless each one carries the page's nonce, so Angular needs to know it. You give it the nonce in one of two runtime ways. `ngCspNonce="…"` on the root element suits servers that template `index.html` per response and put the same value in the header. The `CSP_NONCE` injection token from `@angular/core` suits apps that learn the nonce at runtime and want to cache `index.html`. The token's default factory simply reads the `ngCspNonce` attribute, so the two are the same channel. The guide's third route is the CLI's `autoCsp` option.

code

html · 4 lines
html
<!-- index.html rendered per response; the same value is in the CSP header -->
<body>
  <app-root ngCspNonce="q3Kx9vT2mB7wLr0p"></app-root>
</body>

go deeper

for a junior

Recall that Angular adds component styles to the page at runtime, so a strict style-src blocks them unless Angular knows the nonce.

for a middle

Explain the two channels, ngCspNonce on the root element and the CSP_NONCE token, and that the token's default reads the attribute.

for a senior

Choose the channel from how index.html is served, handle the critical-CSS exception, and note that JSONP, SSR event replay and ShadowDom styles use the same token.

for a principal

Decide where nonce generation lives in the serving stack and whether the team accepts 'unsafe-inline' for styles or pays for per-response HTML.

## The problem: styles Angular creates at runtime A **Content Security Policy (CSP)** is a response header that tells the browser which scripts and styles a page may use. A strict policy drops `'unsafe-inline'` and allows inline code only if it carries a **nonce**, a random value generated for each response and repeated in the header. Angular components declare styles in `styles` or `styleUrl`. Those styles are compiled into the JavaScript bundle and, at runtime, the renderer inserts them into the document: - for the default emulated encapsulation and for `None`, as `<style>` elements in the document head, added once per component type; - for `ShadowDom` encapsulation, as `<style>` elements inside each component's shadow root. To a CSP these are **inline styles**. Under `style-src 'self' 'nonce-abc'` the browser refuses every one that lacks `nonce="abc"`, and the app renders unstyled while the console fills with violations. Angular can add the attribute itself, but only if it knows the value. ## Three ways to hand Angular the nonce The security guide lists three options: | Option | Where the value comes from | Fits when | | :--- | :--- | :--- | | `ngCspNonce` attribute on the root element | server-side templating writes it into `index.html` | the server renders `index.html` per response and can put the same value in the header | | `CSP_NONCE` injection token | any runtime source you provide it from | the nonce is available at runtime and `index.html` should stay cacheable | | `autoCsp` workspace option | the CLI build | you want the CLI to handle the policy for the bootstrap scripts (a build-system subject) | The first two are closer than they look. `CSP_NONCE` is an `InjectionToken<string | null>` whose **default factory** searches the document body for an element with an `ngCspNonce` attribute and returns its value, or `null`. So: 1. If you provide `CSP_NONCE` yourself, your value wins. 2. If you do not, Angular falls back to the `ngCspNonce` attribute. 3. If neither exists, styles are inserted without a nonce. The factory reads the DOM because the value must be ready **very early**, when renderers are created during bootstrap, before any component exists. ## Who else reads the token `CSP_NONCE` is not only for component styles. In Angular 22.2 it is also read by: - the shared styles host and the `ShadowDom` renderer, for `<style>` and external stylesheet `<link>` elements; - `HttpClient`'s JSONP backend, which adds the nonce to the `<script>` it injects; - server-side rendering, which puts it on the small inline script that replays events captured before hydration; - signal forms' input-validity monitor, which inserts a small `<style>` element. So the token is the single place to feed a per-response nonce into everything Angular injects. ## The critical-CSS exception The guide adds a caveat: if the build **inlines critical CSS** into `index.html`, you cannot use `CSP_NONCE`, and should use `ngCspNonce` or `autoCsp` instead. The inlined styles and their loader live in the HTML file itself, outside Angular's runtime, so a value that exists only in the injector cannot reach them. That is also the one case where the guide's minimal policy needs a nonce in `script-src`. ## Common mistakes - **Setting the header but not telling Angular.** The policy is correct, yet every component renders unstyled, because nothing supplied the value to `CSP_NONCE` or `ngCspNonce`. - **Putting the nonce on the wrong element.** The default factory looks for an element carrying `ngCspNonce` inside `document.body`; the guide's pattern puts it on the root component element, such as `<app-root ngCspNonce="…">`. - **Providing the token too late.** Renderers read the value during bootstrap, so the provider belongs in the application's bootstrap providers, not in a lazily loaded route. - **Assuming the nonce covers everything.** Global stylesheets loaded from the origin are allowed by `'self'`; the nonce is for the elements Angular inserts at runtime, and third-party code that injects its own styles needs its own handling. ## What stays your job Angular only **applies** the nonce. Generating a fresh, unguessable value for every response and writing the same value into both the header and the page is the serving layer's job. A nonce frozen into a cached `index.html` gives no protection, and threading it through a CDN is a CSP-deployment subject rather than an Angular one. If you cannot generate nonces at all, the guide's fallback is `'unsafe-inline'` in `style-src`, which gives up protection against injected styles.

  • If you provide neither CSP_NONCE nor ngCspNonce, what does Angular do with component styles?
    It inserts them without a nonce attribute. The token's default factory returns `null` when no element carries `ngCspNonce`, and the renderer only sets the attribute when a value exists. Under a policy without `'unsafe-inline'` for styles, the browser then blocks every component style.
  • Why can't CSP_NONCE cover critical CSS inlined into index.html?
    Critical-CSS inlining writes styles into the HTML file itself during the build or server render, before Angular's injector exists. A value provided through the token only reaches code running inside the app, so the inlined styles need the nonce in the HTML: the guide recommends `ngCspNonce` or `autoCsp` for that case.

saying these in an interview costs you the question

  • Component styles come from the global stylesheet, so CSP never affects them.
  • CSP_NONCE and ngCspNonce are unrelated mechanisms that must both be set.
  • Angular generates a fresh nonce itself on every page load.
  • A nonce baked into a cached index.html still protects the app.
  • CSP_NONCE works for critical CSS inlined into index.html.