Skip to content

Instantly share code, notes, and snippets.

@minagawah
Last active July 23, 2022 02:40
Show Gist options
  • Save minagawah/bc56b1dae8e3a967788c255a1032d1ae to your computer and use it in GitHub Desktop.
Save minagawah/bc56b1dae8e3a967788c255a1032d1ae to your computer and use it in GitHub Desktop.
Generate your own "nonce" when you see it is not inserted for "style data-emotion"...

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>
@seansean11
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment