Skip to main content
Version: Next

Using Content Security Policy Nonces in Stencil

Content Security Policies (CSPs) can help protect an application from Cross-Site Scripting (XSS) attacks by adding a security layer to help prevent unauthorized code from running in the browser.

An application that is served with a CSP other than 'unsafe-inline' and contains web components without a Shadow DOM will likely run into errors on load. This is often first detected in the browser's console, which reports an error stating that certain styles or scripts violate the effective CSP.

To resolve this issue, Stencil supports using CSP nonces in many of the output targets.

caution

NOTE: CSPs and some CSP strategies are not supported by certain browsers. For a more detailed list, please see the CSP browser compatibility table.

How to Use a Nonce

The actual generation of the nonce value and enforcement of the correct CSP are not the responsibility of Stencil. Instead, the server of the application will need to generate the nonce value for each page view, construct the CSP, and then correctly handle passing the generated nonce to Stencil based on which output target is being consumed.

There are many resources available that walk through setting up a CSP and using the nonce behavior. This article walks through the process using Nginx and Webpack. Obviously, these resources don't account for the Stencil specifics, but any specifics will be called out in this guide.

Per the MDN Guide on nonces, a nonce should be "a random base64-encoded string of at least 128 bits of data from a cryptographically secure random number generator".

Output Targets

Using nonces may differ slightly between output targets, so please be sure to use the correct pattern based on the context in which your Stencil components are consumed.

Dist

Consuming a nonce in the dist output target is easy using the provided setNonce helper function. This function is exported from the index file of the output target's designated output directory.

This function simply accepts the nonce string value that you want set for every style and script tag.

This is an example of consuming the dist output in an Angular app's entrypoint:

// main.ts

import { defineCustomElements, setNonce } from 'my-lib/loader';

// Will set the `nonce` attribute for all scripts/style tags
// i.e. will run styleTag.setAttribute('nonce', 'r4nd0m')
// Obviously, you should use the nonce generated by your server
setNonce('r4nd0m');

// Generic Angular bootstrapping
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch(err => console.log(err));

defineCustomElements();

Custom Elements

Consuming a nonce in the dist-custom-elements output target is easy using the provided setNonce helper function. This function is exported from the index file of the output target's designated output directory.

This function simply accepts the nonce string value that you want set for every style and script tag.

This is an example of consuming the dist-custom-elements output in an Angular app's entrypoint:

// main.ts

import { defineCustomElements, setNonce } from 'my-lib/dist/components';
// Assume `customElementsExportBehavior: 'auto-define-custom-elements'` is set
import 'my-lib/dist/components/my-component';

// Will set the `nonce` attribute for all scripts/style tags
// i.e. will run styleTag.setAttribute('nonce', 'r4nd0m')
// Obviously, you should use the nonce generated by your server
setNonce('r4nd0m');

// Generic Angular bootstrapping
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch(err => console.log(err));

WWW

Unfortunately, setting nonce attributes gets a bit trickier when it comes to SSR and SSG. As a nonce needs to be unique per page view, it cannot be defined/set at build time. So, this responsibility now falls on the hydrate app's execution of runtime code.

SSR

Since there is not an easy way (or any way) of exposing and executing helper functions to manipulate the outcome of the runtime code, Stencil has fallback behavior for pulling the nonce off of a meta tag in the DOM head.

So, for SSR, your app can simply inject a meta element into the header on each page request. Yes, this does involve some manual configuration for the code served by your server. To work correctly, the created tag must be generated as follows:

<meta name="csp-nonce" content="{ your nonce value here }" />

This isn't a security risk because, for an attacker to execute a script to pull the nonce from the meta tag, they would have needed to know the nonce ahead of the script's execution.

SSG

Stencil cannot support CSP nonces with SSG. Because all of the code is generated during pre-rendering, Stencil doesn't generate the style or script tags at runtime. If an application wants to leverage nonces in SSG, they can build a mechanism to scrape the pre-rendered code and apply the attribute server-side before it is served to the client.