Search code examples
google-chromegoogle-chrome-extensioncorscross-origin-read-blocking

How to make a cross-origin request in a content script (currently blocked by CORB despite the correct CORS headers)?


I am developing a Chrome extension which makes requests from certain websites to an API I control. Until Chrome 73, the extension worked correctly. After upgrading to Chrome 73, I started getting the following error:

Cross-Origin Read Blocking (CORB) blocked cross origin response http://localhost:3000/api/users/1 with MIME type application/json

According to Chrome's documentation on CORB, CORB will block the response of a request if all of the following are true:

  1. The resource is a "data resource". Specifically, the content type is HTML, XML, JSON

  2. The server responds with an X-Content-Type-Options: nosniff header, or if this header is omitted, Chrome detects the content type is one of HTML, XML, or JSON from inspecting the file

  3. CORS does not explicitly allow access to the resource

Also, according to "Lessons from Spectre and Meltdown" (Google I/O 2018), it seems like it may be important to add mode: cors to fetch invocations, i.e., fetch(url, { mode: 'cors' }).

To try to fix this, I made the following changes:

First, I added the following headers to all responses from my API:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Origin: https://www.example.com

Second, I updated my fetch() invocation on the extension to look like this:

fetch(url, { credentials: 'include', mode: 'cors' })

However, these changes didn't work. What can I change to make my request not be blocked by CORB?


Solution

  • Based on the examples in "Changes to Cross-Origin Requests in Chrome Extension Content Scripts", I replaced all invocations of fetch with a new method fetchResource, that has a similar API, but delegates the fetch call to the background page:

    // contentScript.js
    function fetchResource(input, init) {
      return new Promise((resolve, reject) => {
        chrome.runtime.sendMessage({input, init}, messageResponse => {
          const [response, error] = messageResponse;
          if (response === null) {
            reject(error);
          } else {
            // Use undefined on a 204 - No Content
            const body = response.body ? new Blob([response.body]) : undefined;
            resolve(new Response(body, {
              status: response.status,
              statusText: response.statusText,
            }));
          }
        });
      });
    }
    
    // background.js
    chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
      fetch(request.input, request.init).then(function(response) {
        return response.text().then(function(text) {
          sendResponse([{
            body: text,
            status: response.status,
            statusText: response.statusText,
          }, null]);
        });
      }, function(error) {
        sendResponse([null, error]);
      });
      return true;
    });
    

    This is the smallest set of changes I was able to make to my app that fixes the issue. (Note, extensions and background pages can only pass JSON-serializable objects between them, so we cannot simply pass the Fetch API Response object from the background page to the extension.)

    Background pages are not affected by CORS or CORB, so the browser no longer blocks the responses from the API.

    Warning! The URL must be added in manifest.json:

    • For ManifestV3 in "host_permissions": ["*://*.example.com/"]
    • For ManifestV2 in "permissions": ["*://*.example.com/"]