google-chromecorswebpack-dev-serverpreflightprivate-network-access

CORS error on request to localhost dev server from remote site


On Friday I had a working dev environment. On Monday I had a broken one. I encountered this error message in the Chrome dev-tools console for all my assets:

Access to CSS stylesheet at 'http://localhost:8080/build/app.css' from origin 'http://example.com' has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private adress space local.

Is there any quick fix for this? I tried setting access-control-allow-origin in my webpack devServer.headers config to no avail:

config.devServer.headers = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
}

Solution

  • Original Answer

    I finally found the answer, in this RFC about CORS-RFC1918 from a Chrome-team member. To sum it up, Chrome has implemented CORS-RFC1918, which prevents public network resources from requesting private-network resources - unless the public-network resource is secure (HTTPS) and the private-network resource provides appropriate (yet-undefined) CORS headers.

    There's also a Chrome flag you can change to disable the new behavior for now: chrome://flags/#block-insecure-private-network-requests

    Disabling that flag does mean you're re-opening the security hole that Chrome's new behavior is meant to close.


    Update 2021: A few months after I posted this question, the flag I referenced in my original answer was removed, and instead of disabling a security feature I was forced to solve the problem more satisfactorily by serving assets over HTTPS.

    Update 2022: Chrome 98 is out, and it introduces support for Preflight requests. According to the announcement, failed requests are supposed to produce a warning and have no other effect, but in my case they are full errors that break my development sites. So I had to add middleware to teach webpack-dev-server how to serve preflight requests.

    Private Network Access (formerly CORS-RFC1918) is a specification that forbids requests from less private network resources to more private network resources. Like HTTP to HTTPS, or a remote host to localhost.

    The ultimate solution was to add a self-signed certificate and middleware which enabled requests from my remote dev server to my localhost webpack-dev-server for assets.

    Generate certificates

    cd path/to/.ssl
    npx mkcert create-cert
    

    Configure webpack-dev-server to use certificates and serve preflight requests

    module.exports = {
      //...
      devServer: {
        https: {
          key: readFileSync("./.ssl/cert.key"),
          cert: readFileSync("./.ssl/cert.crt"),
          cacert: readFileSync("./.ssl/ca.crt"),
        },
    
        allowedHosts: ".example.dev", // should match host in origin below
        setupMiddlewares(middlewares, devServer) {
          // Serve OPTIONS requests
          devServer.app.options('*', (req, res) => {
            // Only serve if request has expected origin header
            if (/^https:\/\/example\.dev$/.test(req.headers.origin)) {
              res.set({
                "Access-Control-Allow-Credentials": "true",
                "Access-Control-Allow-Private-Network": "true",
                // Using * results in error if request includes credentials
                "Access-Control-Allow-Origin": req.headers.origin,
              })
    
              res.sendStatus(200)
            }
          }
    
          return middlewares
        }
      }
    }
    

    Trust certificates

    1. Right click ca.crt in Windows Explorer and select Install Certificate
    2. Select Current User.
    3. Choose Place all certificates in the following store, then Browse..., and select Trusted Root Certification Authorities.
    4. Finish.

    Firefox-specific instructions

    Firefox doesn't respect your authoritah! by default. Configure it to do so with these steps:

    1. Type about:config into the address bar
    2. Search for security.enterprise_roots.enabled
    3. Toggle the setting to true