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.
CSPs and some CSP strategies are not supported by legacy browsers such as Internet Explorer. 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 `autoDefineCustomElements` is enabled enabled
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.