Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
129 lines (95 sloc) 5.02 KB

Setting up Content Security Policy with JSS

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.

  1. 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-Policy header should now contain the nonce:

    default-src 'self'; style-src 'self' 'nonce-N2M0MDhkN2EtMmRkYi00MTExLWFhM2YtNDhkNTc4NGJhMjA3';
    
  2. 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.

  3. First, create the template HTML file.

      <head>
        <meta property="csp-nonce" content="{{ styleNonce }}">
      </head>
      ...
  4. Now update the server to render the template with the styleNonce variable to be interpolated with the nonce generated from our middleware res.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 a content attribute 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.

Setting the nonce attribute in styling loaded by Webpack

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: {attrs: {nonce: '{{ styleNonce }}'}}
          },
          'css-loader'
        ]
      },
      {
        test: /\.scss$/,
        use: [
          {
            loader: 'style-loader',
            options: {attrs: {nonce: '{{ styleNonce }}'}}
          },
          'css-loader',
          'sass-loader'
        ]
      }
    ]
  }
}

Resources