Content Security Policy (CSP) is a way of whitelisting what resources the browser should allow to load (and reach out to). There are many excellent resources on CSP for more information. This explanation is a good place to start.
In general, you should start with Content-Security-Policy: default-src 'self'; and enable things as needed from there.
In the case of JSS, we need to set the style-src CSP directive. We don't want to just set it to unsafe-inline which will allow everything.
The solution is to include a nonce (number used once):
MDN/CSP/style-src notes the following:
'nonce-{base64-value}' A whitelist for specific inline scripts using a cryptographic nonce (number used once). The server must generate a unique nonce value each time it transmits a policy. It is critical to provide an unguessable nonce, as bypassing a resource’s policy is otherwise trivial. See unsafe inline script for example.
Because the nonce must be generated to be unique and random for every request, this is not something that we can do at build time. Previously the docs suggested using the __webpack_nonce__ variable with Webpack. However that is insecure because it never changes, so it could be trivially bypassed by an attacker, as noted in the Mozilla docs above.
To communicate the nonce value to JSS, we're going use some basic templating with an express server.
-
In the server startup, we'll add middleware to generate the nonce.
// server.js import helmet from 'helmet' import uuidv4 from 'uuid/v4' import express from 'express' const app = express() app.use((req, res, next) => { // nonce should be base64 encoded res.locals.styleNonce = Buffer.from(uuidv4()).toString('base64') next() }) app.use( helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"], /* ... */ styleSrc: ["'self'", (req, res) => `'nonce-${res.locals.styleNonce}'`] } }) )
(The above example uses Helmet to set CSP directives).
When loading the initial index page, the
Content-Security-Policyheader should now contain the nonce:default-src 'self'; style-src 'self' 'nonce-N2M0MDhkN2EtMmRkYi00MTExLWFhM2YtNDhkNTc4NGJhMjA3'; -
Now we want to include this value in our page so that JSS can pick it up at runtime.
You can use any templating engine, or if you have SSR, that should work too. For this example, we'll use the Nunjucks template engine.
-
First, create the template HTML file.
<head> <meta property="csp-nonce" content="{{ styleNonce }}"> </head> ...
-
Now update the server to render the template with the
styleNoncevariable to be interpolated with the nonce generated from our middlewareres.locals.styleNonce.import express from 'express' const app = express() app.get('/', (req, res) => { res.render('index', {styleNonce: res.locals.styleNonce}) })
The tag must have
property="csp-nonce"and have acontentattribute with the string value to use as the nonce.Now JSS can apply the nonce to
<style />elements:<style nonce={nonce-value} />At this point, the style blocks generated by JSS should render with the nonce value (
<style nonce />) when you inspect the page.The browser might not show the nonce value inside the style tag when you inspect the page, but it's there regardless.
You might still have some CSP violations if you use style/css/sass in addition to JSS. To fix this, set Webpack's style-loader to also set the nonce attribute to the placeholder template that we used above {{ styleNonce }}. This way, when the express server renders your HTML page as a template, it will fill in the proper nonce value.
// webpack config
const config = {
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
options: {attributes: {nonce: '{{ styleNonce }}'}}
},
'css-loader'
]
},
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
options: {attributes: {nonce: '{{ styleNonce }}'}}
},
'css-loader',
'sass-loader'
]
}
]
}
}