9

I'm struggling with serving a build created with "create-react-app" using Express with Helmet. I'm getting several errors in the explorer console related to Content Security Policy:

csp-errors

Of course, it isn't showing the app. I noticed that if a remove Helmet as middleware in Express it works but that's not the solution I want. This is my server code:

const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const bodyParser = require('body-parser');

/**
 * Server Configuration
 */

const whitelist = [];

const app = express();

// Express Configurations

// Enable reverse proxy support in Express. This causes the the "X-Forwarded-Proto" header field to be trusted so its
// value can be used to determine the protocol. See // http://expressjs.com/api#app-settings for more details.

app.enable('trust proxy');

app.use(morgan('dev')); // Log every request to the console
app.use(helmet()); // Configure secure Headers
app.use(bodyParser.urlencoded({ extended: false })); // Enable parsing of http request body
app.use(bodyParser.json());

// CORS Configuration

const corsOptions = {
  origin: (origin, callback) => {

    if (whitelist.indexOf(origin) !== -1 || !origin) {

      callback(null, true);

    } else {

      callback(new Error('Not allowed by CORS'));

    }

  },
};

app.use(cors(corsOptions)); // Allow CORS

/**
 * Launcher method
 */

app.start = () => {

  // start node server
  const port = process.env.PORT || 3000;
  app.listen(port, () => {

    console.log(`App UI available http://localhost:${port}`);
    console.log(
      `Swagger UI available http://localhost:${port}/swagger/api-docs`,
    );

  });

};

/**
 * App Initialization
 */

function initializeApp(readyCallback) {

  readyCallback(null, app);

}

module.exports = (readyCallback) => {

  initializeApp(readyCallback);

};

Can anyone give me a hand? Thanks in advance!

3 Answers3

22

Helmet maintainer here.

This is happening because of something called Content Security Policy, which Helmet sets by default. To solve your problem, you will need to configure Helmet's CSP.

MDN has a good documentation about CSP which I would recommend reading for background. After that, take a look at Helmet's README to see how to configure its CSP component.

To give some help specific to this question, let's take a look at one error you're seeing:

Content Security Policy: This page's settings blocked the loading of a resource at inline ("script-src").

This error is telling you that the script-src directive of your CSP does not allow inline JavaScript, and so it was blocked.

This is considered "inline" JavaScript:

<script>console.log('hello world!')</script>

This, however, is not:

<script src="/foo.js"></script>

There are several ways to fix this:

  1. Add a hash or nonce to the inline <script> and use that in your CSP. See this example on MDN for help.

  2. Refactor your app to avoid inline scripts entirely.

  3. Update your CSP to allow unsafe inline scripts. You'd do something like this:

    app.use(
      helmet({
        contentSecurityPolicy: {
          directives: {
            ...helmet.contentSecurityPolicy.getDefaultDirectives(),
            "script-src": ["'self'", "'unsafe-inline'", "example.com"],
          },
        },
      })
    );
    

    Note that this is considered unsafe.

  4. Disable CSP. This is the most dangerous option so I don't recommend it.

    app.use(
      helmet({
        contentSecurityPolicy: false,
      })
    );
    

Your other errors, such as the fonts.googleapis.com error, refer to default-src, which is the fallback if a directive is not specified.

In summary: to solve your problem, you will need to tell Helmet to configure your CSP.

Evan Hahn
  • 12,147
  • 9
  • 41
  • 59
  • Very clear, thanks for your answer, it worked for me. The only thing that gave me problems was the line: `...helmet.contentSecurityPolicy.getDefaultDirectives()` It looks that if I spread the default directives Helmet gives an error related to a duplicated directive. I've just ignored it and it works. – Nicolas Mauricio Cuadrado Nov 10 '20 at 01:50
  • @NicolasMauricioCuadrado If it worked for you, you should mark it as the accepted answer. Their answer also helped me (a beginner to security) solved my problem. – SnekNOTSnake Mar 06 '21 at 13:50
  • Even with `app.use(helmet({ contentSecurityPolicy: false, }));` firefox yells about "Content Security Policy: The page’s settings blocked the loading of a resource" so... what's going on? – Mike 'Pomax' Kamermans Feb 18 '23 at 20:59
6

Got here via google with the same question. I didn't want to lower any of the security settings in helmet so I changed my react build config. Simply add the line

INLINE_RUNTIME_CHUNK=false

to your .env in the react app root directory. Then when you run npm run build to build the app, all inlined scripts will be removed and will no longer violate the CSP. This does add one extra initial HTTP GET request when first loading the site but seems to be worth the security benefits in my opinion.

AlaskaJoslin
  • 760
  • 8
  • 14
  • Hi, this could be a better solution. I'm not sure about the extra GET request, can you elaborate a bit on that? please. Thanks! – Nicolas Mauricio Cuadrado Dec 30 '20 at 18:36
  • Sure thing, my understanding from the docs https://create-react-app.dev/docs/production-build is that the reason the inlining is added in the first place is to place the bootstrapping code in the "index.html". This means that the app can immediately request the rest of the app after getting the index.html. While this could be important for some use cases I think it's a bit of premature optimization for most of us. – AlaskaJoslin Dec 30 '20 at 20:37
0

Here is a third solution. Change your build script in package.json to following:

"build": "GENERATE_SOURCEMAP=false node scripts/build.js"

Luna
  • 1,168
  • 8
  • 17