DEV Community

Cover image for Browser extension - Secure http requests from content scripts
Quentin Ménoret
Quentin Ménoret

Posted on • Updated on

Browser extension - Secure http requests from content scripts

I just published a new extension on Chrome and Firefox that allows anyone to run Code Tours from the Github UI. More information about Code Tours and the extension in this blog post.

I thought it would be nice to write a series about how you could do exactly the same, step by step.

This third blog post will focus on sending cross origin http requests from a Content script.

The problem

Remember that code tour extension I was mentioning just before? Let's try to implement the first bit. What we want is to be able to retrieve the content of code tours (json files) so we can process them. You’d think it should be as simple as using fetch with the right URL, right? Well, it’s a bit more complicated than that.

A naive approach

First, we need to find all code tours definition files. Once we’re on the .tours directory in Github, we can use a selector:

// Wait for the page to be ready
document.addEventListener("DOMContentLoaded", function(){
  Array.from(
      // Find all links to tour files
      document.querySelectorAll('div[role=row] > div[role="rowheader"] > span > a').values(),
    ).map(
      async (parentElement) => {
        const title = parentElement.getAttribute('title')
        const href = parentElement.getAttribute('href')


        // Now we want to query the file content as a raw string.
        // In Github, this means fetching the file using “raw” instead of “blob”
        const codeTourUrl = href.replace('blob', 'raw')

        // A code tour is a json object, we can use the fetch API to receive an object
        const content = await fetch(codeTourUrl).then((response) => response.json())
        console.log(title, content)
  })
})
Enter fullscreen mode Exit fullscreen mode

Copy this code in the Content Script file you've created in this post:

If you refresh the extension, it will get executed in any page you load. Go to the .tours folder of a project and open the console. You will see either one of these:

  • On Chrome, the list of Code Tours and their contents will be loaded
  • On Firefox, you will see an error and the http request will be denied

Forwarding the requests to the background

With this code, we should be able to retrieve all the code tours definition files. Unfortunately, Github will redirect us during this request. It won’t work on Firefox as this is a Cross Origin Request.

In general, you shouldn’t use fetch from your content script. The right way to process this will be to forward the query to the background script, so it can perform the request for you:

Let’s create a function allowing us to forward requests to the background script:

function forwardRequest(message) {
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage(message, (response) => {
      if (!response) return reject(chrome.runtime.lastError)
      return resolve(response)
    })
  })
}
Enter fullscreen mode Exit fullscreen mode

In the background script, we need to handle this request:

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    // sendResponse can be used to send back a result to the content script
    fetch(`https://github.com/${request.url}`)
         .then((response) => response.json())
         .then((codeTourContent) => sendResponse(codeTourContent))
    // As we will reply asynchronously to the request, we need to tell chrome to wait for our response
    return true
})
Enter fullscreen mode Exit fullscreen mode

⚠️ Be careful with how you forward requests to your background script. You should make sure you won’t trigger requests to random URLs as this is fully authenticated. In this case we should verify the URL (make sure it’s safe) and only access GET requests. As you can see here we enforce the usage of the Github domain, but you should probably include more checks. If possible, build the URL yourself in the background script to be surer to target the exact resource you need.

Now that we have this code, we can replace the call to fetch in the background script with a call to forwardRequest.

Array.from(
    document.querySelectorAll('div[role=row] > div[role="rowheader"] > span > a').values(),
  ).map(
    async (parentElement) => {
        const title = parentElement.getAttribute('title')
        const href = parentElement.getAttribute('href')
        const codeTourUrl = href.replace('blob', 'raw')

        // Now forward request will behave like fetch
        const content = await forwardRequest({ url: codeTourUrl })
        console.log(title, content)
})
Enter fullscreen mode Exit fullscreen mode

This still won’t work on Firefox, as it will prevent the background script from making any requests to random hostnames. In order to fix this, you will need to ask the permission to query github.com and render.githubusercontent.com (github redirects you to this domain when querying a raw file) from the background script.

Just add this into your manifest file:

{
    "permissions": ["https://render.githubusercontent.com/*", "https://github.com/*"]
}
Enter fullscreen mode Exit fullscreen mode

Reload the extension, and it works!

Conclusion

We just deep dived into one really important aspect of Browser Extension: securely retrieving data from content script. In the next post, we’ll use the data to build a feature! Feel free to follow me here if you want to check the next one when it's out:


Photo by Ricardo Gomez Angel on Unsplash

Top comments (0)