18

From my understanding of Content Security Policy, the nonce has to change on every request. That means (I think) it must be generated at run-time on the client, not at build-time in the Webpack config. I've tested the webpack_nonce functionality in my app and it works great.

Unfortunately, I'm not sure how to get that value, generated at run-time on the client, to the actual CSP policy, which is either set as a meta-tag in the index.html file (or some equivalent) or on the server itself.

I suppose you could set the CSP meta-tag dynamically on the client, but that seems like a security risk. I've experimented with the csp-webpack-plugin, which calculates hashes of files at build-time and then adds them to the index.html. This process makes sense to me, it just didn't support our use case.

I just feel like I'm missing something with using webpack_nonce.

Paul Z
  • 301
  • 1
  • 2
  • 4

1 Answers1

11

We were able to get a dynamic nonce by having webpack build our index page (e.g via HtmlWebpackPlugin) as a template then serving it dynamically. This way, you can set __webpack_nonce__ to an interpolation expression like <%=nonce%> and the server view engine can sub in your dynamic nonce at page-load time. For example, if you're using express:

Webpack config:

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: 'index.js',
  output: {
    path: __dirname + '/dist',
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: __dirname + '/dist/index.ejs',
    })
  ]
}

Webpack entry point (index.js):

__webpack_nonce__ = '<%=nonce%>';

Express app:

// Generate dynamic nonce on each request cycle
const uuid = require('node-uuid');
app.use((req, res, next) => {
  res.locals.nonce = uuid.v4();
  next();
});

app.set('view engine', 'ejs');
app.route('/').get((req, res, next) => {
  res.render('path/to/index.ejs', { nonce: res.locals.nonce });
});

The injected <script> tags will have the literal attribute nonce=<%=nonce%> appended, which the server will then interpolate when serving your page.

Note that if you use a custom template with HtmlWebpackPlugin, you might want to set a different interpolation delimiter for ejs otherwise Webpack will interpolate the nonce expression at build time instead of runtime.

Express app:

const ejs = require('ejs);
ejs.delimiter = '?'; // Means instead use __webpack_nonce__ = '<?=nonce?>'
Christian Yang
  • 452
  • 3
  • 12
  • Hey christian, what about styles? for csp it also requires for style tags. Would you do something simliar here with something like minicss plugin? – Huy Tran Mar 14 '21 at 15:03
  • I haven't tried directly, but it looks like the `style-loader` plugin supports `__webpack_nonce__` so a similar approach should work: https://webpack.js.org/loaders/style-loader/#__webpack_nonce__ – Christian Yang Mar 18 '21 at 03:13
  • 2
    Dear Christian, could you by any chance please post a minimal example on GitHub please? – Nikolay Schamberg Jun 24 '21 at 09:35