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.
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?>'