Skip to main content
Version: v5 (Preview)

Using Content Security Policy Nonces in Stencil

A Content Security Policy (CSP) helps protect an application from Cross-Site Scripting (XSS) attacks by restricting which scripts and styles the browser will run.

Stencil adds some of your components' styles to the page in <style> elements. A policy whose style-src doesn't allow 'unsafe-inline' blocks those elements, and the browser's console reports that an inline style violates the policy. This affects:

Shadow DOM components rendered in the browser aren't affected, because their styles are applied as constructable stylesheets.

To let these styles through, Stencil can add a nonce to every <style> element it creates.

Generating a Nonce​

Generating the nonce and sending the policy are your server's job. For each page view, the server creates a new nonce, includes it in the Content-Security-Policy header, and passes it to Stencil in one of the ways below:

Content-Security-Policy: style-src 'self' 'nonce-r4nd0m'

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".

Passing the Nonce to Stencil​

With a Meta Tag​

Add a meta element to the document's head on each page request:

<meta name="csp-nonce" content="r4nd0m" />

Stencil reads it when it creates a <style> element. This works for every output target, and needs no JavaScript from the application.

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

With setNonce()​

Applications that register your components from JavaScript can call setNonce() instead. Call it before the components are defined.

With the loader-bundle output, it's exported from the loader:

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

setNonce('r4nd0m');
defineCustomElements();

With the standalone output, it's exported from the output's index file:

import { setNonce } from 'my-lib/dist/standalone';
import { defineCustomElement } from 'my-lib/dist/standalone/my-component';

setNonce('r4nd0m');
defineCustomElement();

If your library sets generateExportMaps, these imports are my-lib/loader and my-lib/standalone.

Server-Side Rendering​

Server-rendered HTML contains <style> elements for the components on the page, and these need the nonce as well. Include the meta tag in the document you pass to renderToString(), and Stencil adds the nonce to them:

import { renderToString } from 'my-lib/dist/ssr';

const { html } = await renderToString(`
<html>
<head>
<meta name="csp-nonce" content="${nonce}" />
</head>
<body>
<my-component></my-component>
</body>
</html>
`);

The meta tag is part of the rendered HTML, so the components pick up the same nonce when they load in the browser.

Static Site Generation​

Stencil can't add nonces to prerendered pages. A nonce has to be different for each page view, and a prerendered page is generated once, at build time. To use nonces with prerendered pages, your server has to add the nonce attribute to each <style> element in the page as it serves it.