javascripthttpscorsfile-uri

Using the 'integrity' attribute for script tags while avoiding CORS errors over the file-uri protocol


I want to build a lightweight single-page app that people can use locally over the file:// protocol (i.e. simply by downloading the project archive file, unpacking it and double clicking on index.html), as well as on the web (https://).

All my dependencies are stored within my project (e.g. in a /js folder), including the third-party libraries I use (namely, AlpineJs and JSZip).

So far so good, it works in both scenarios (file and https).

I also want to use the integrity attribute on the <script> elements, so that users can verify that I did not temper with the code of those libraries. This gives me something like:

<script
  src="js/jszip.min.js"
  integrity="sha512-XMVd28F1oH/O71fzwBnV7HucLxVwtxf26XV8P4wPk26EDxuGZ91N8bsOttmnomcCD3CS5ZMRL50H0GgOHvegtg=="
  crossorigin="anonymous"
  referrerpolicy="no-referrer"
></script>
<script
  src="js/alpinejs.min.js"
  defer
  integrity="sha512-FUaEyIgi9bspXaH6hUadCwBLxKwdH7CW24riiOqA5p8hTNR/RCLv9UpAILKwqs2AN5WtKB52CqbiePBei3qjKg=="
  crossorigin="anonymous"
  referrerpolicy="no-referrer"
></script>

However, as soon as I start using those attributes, the browser throws a CORS error and refuses to load the files, when accessing the app over the file:// protocol:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at file:///.../js/jszip.min.js. (Reason: CORS request not http). [Learn More]

I'm not entirely familiar with the crossorigin and referrerpolicy attributes. Reading the doc about them, I didn't find anything that helped me in this regard. I'm don't think they are needed. However if I remove them, I get a different error:

“file:///.../js/alpinejs.min.js” is not eligible for integrity checks since it’s neither CORS-enabled nor same-origin.

Is there a way to set it up so that it works both over https:// and file://, while keeping the integrity attribute?


Solution

  • You cannot have the integrity from file protocol due to the CORS needed to verify the integrity.

    You can load the script locally without the integrity like this

    const isFileProtocol = window.location.protocol === 'file:';
    
    const scripts = [{
        src: 'js/jszip.min.js',
        integrity: 'sha512-XMVd28F1oH/O71fzwBnV7HucLxVwtxf26XV8P4wPk26EDxuGZ91N8bsOttmnomcCD3CS5ZMRL50H0GgOHvegtg==',
        crossorigin: 'anonymous',
        referrerpolicy: 'no-referrer',
        defer: false,
      },
      {
        src: 'js/alpinejs.min.js',
        integrity: 'sha512-FUaEyIgi9bspXaH6hUadCwBLxKwdH7CW24riiOqA5p8hTNR/RCLv9UpAILKwqs2AN5WtKB52CqbiePBei3qjKg==',
        crossorigin: 'anonymous',
        referrerpolicy: 'no-referrer',
        defer: true,
      },
    ];
    
    scripts.forEach(({ src, integrity, crossorigin, referrerpolicy, defer }) => {
      const script = document.createElement('script');
      script.src = src;
      if (!isFileProtocol) {
        script.integrity = integrity;
        script.crossOrigin = crossorigin;
      }
      if (referrerpolicy) {
        script.referrerpolicy = referrerpolicy;
      }
      if (defer) {
        script.defer = true;
      }
      document.head.appendChild(script);
    });