This is an excerpt from README in my private repository.
Since it may help those who are struggling to get rid of CSP errors for data-emotion
, here you go:
Although csp-html-webpack-plugin
automatically inserts CSP (Content Security Policy) meta tags in your generated HTML page, you will see CSP warns against the rules.
While it inserts "nonce" to all the style
tags, you see <style data-emotion></style>
being left out, not inserted with "nonce" at all.
emotion
provides a CSP support via @emotion/cache
.
The idea is to NOT allowing csp-html-webpack-plugin
insert "nonce" for you, but you manually generate your own "nonce", and somehow manage to pass it to the app, so that it will embed the "nonce" to the page.
So, as csp-html-webpack-plugin
instructs you, first of all, you need this in your template:
<meta http-equiv="Content-Security-Policy" content="%%CSP_CONTENT%%">
and, in your webpack config:
const crypto = require('crypto');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CspHtmlWebpackPlugin = require('csp-html-webpack-plugin');
const emotionalNonce = crypto.randomBytes(16).toString('base64');
module: {
rules: [
....
....
// This is important.
// Even for 'development', make sure to output CSS files.
// Otherwise, there will be no way we can insert "nonce"
// to inline styles embedded in the generated HTML page.
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
// 'style-loader'
'css-loader',
'postcss-loader',
],
},
....
....
],
},
....
....
plugins: [
new webpack.DefinePlugin({
'process.env.EMOTIONAL_NONCE': JSON.stringify(emotionalNonce),
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[id].[hash].css',
}),
new CspHtmlWebpackPlugin(
{
'base-uri': "'self'",
'default-src': [
"'self'",
'localhost:*',
'https://my-sample-website.com',
'https://*.my-sample-website.com',
],
'object-src': "'none'",
'script-src': ["'unsafe-inline'", "'self'", "'unsafe-eval'"],
'style-src': [
"'unsafe-inline'",
"'self'",
"'unsafe-eval'",
`'nonce-${emotionalNonce}'`,
],
},
{
enabled: true,
hashEnabled: {
'script-src': true,
'style-src': false, // Doesn't matter if it's true, or not.
},
nonceEnabled: {
'script-src': true,
'style-src': false, // Doesn't matter if it's true, or not.
},
}
),
....
....
then, finally, to your entry script:
import { CacheProvider, jsx, css } from '@emotion/core';
import createCache from '@emotion/cache';
const emotionalNonce = process.env.EMOTIONAL_NONCE;
const styleCache = createCache({
key: 'my-own-prefix',
nonce: emotionalNonce,
});
ReactDOM.render(
<CacheProvider value={styleCache}>
<Router>
<App />
</Router>
</CacheProvider>,
document.getElementById('root')
);
voilà!
<meta charset="utf-8">
<link rel="shortcut icon" href="/assets/favicon.ico">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="base-uri 'self'; object-src 'none'; script-src 'unsafe-inline' 'self' 'unsafe-eval' 'nonce-tOR2nQcbrcVWFqpTNnwC+g==' 'nonce-rCcr/6gsDxazClNhr+aBUw==' 'nonce-tusoJSj0QbfZg2tsVtgQHg==' 'nonce-xPGdZV0Vl2LwYQ/437ePSg=='; style-src 'unsafe-inline' 'self' 'unsafe-eval' 'nonce-CiV9ZodLnW9q1dMCqYc5QQ=='; default-src 'self' localhost:* https://my-sample-website.com https://*.my-sample-website.com">
<title>My Sample Website</title>
<link href="/assets/css/3.c88a3dd7a5c4b9bbbe3c.css" rel="stylesheet">
<style data-emotion="my-own-prefix" nonce="CiV9ZodLnW9q1dMCqYc5QQ=="></style>
An issue with this approach is that you are generating a nonce per static build, whereas the nonce should be generated each time the index.html is requested. Throughout the lifecycle of each build, the nonce would be the same for every user visit. Wouldn't this defeat the purpose of using nonces?