Choosing Origin header from background script

greetings, :email:

So I’m making an add-on (manifest v3 if that matters) to have push notifications for a specific website :bell:. I made a background script which is regularly fetching a page from the website which gives the data I want (number of new notifications).

Problem being: the website obviously doesn’t give any information if the Origin header of the request is not the website itself. So I have to do a request with the good Origin header, so:

  • I tried to modify it in fetch, obviously doesn’t work;
  • then I tried with the webRequest api, can’t change Origin either;
  • then I thought about fetching the data in the context of the page, but the point of push notifications is that it’s always running even when there is no page open;
  • then I searched mozilla forum addons on lilo search engine

The add-on is supposed to be able to do that if it has the good host_permissions, but I find no way to do it in a background script (or anything that runs in background). So ummm can you help me please? :sparkles:

How odd. As I recall web servers generally treat requests that don’t have an origin header as having come from the website itself.

Modifying request headers is one of the purposes of the webRequest and declarativeNetRequest APIs. There are some limitations on what headers you can modify, but to my knowledge “Origin” is not one of the restricted headers.

Ah! Have you granted your extension’s host permissions after loading it in Firefox? I don’t mean requesting the host permissions in your manifest. I mean opening about:addons, finding your extension, selecting the “permissions” tab, and enabling permissions for your extensions. Host permission are not granted by default in Manifest V3: see the migration guide for additional details.

If that doesn’t do the trick, please share a demo extension that we can debug and discuss.

I think the point of Origin is for the browser to tell to the server what entity the request really comes from. So it makes sense to me that the server doesn’t accept no origin as the website itself, and that we can’t modify it. But since my extension has the good host_permissions (I did activate it manually since I use v3).

I can do a request with the good Origin if I inject a content script in a page from the website, but I should be able to do it from a background script.

I used the website echo.free.beeceptor.com that echoes all the headers of the request. So the goal of this demo extension would be to fetching the website every 10 secs and display the Origin header echoed from the website in a notification (even when Firefox is closed), and that Origin being the website itself (the part I don’t know how to do).

manifest.json:
{
“manifest_version”: 3,

	"name": "testext",
	"description": "Extension de test",
	"version": "547",
	"author": "frzysk",

	"background": {
		"scripts": [
			"background.js"
		]
	},

	"host_permissions": [
		"*://echo.free.beeceptor.com/*"
	],
	"permissions": [
		"notifications"
	]
}

background.js:
“use strict”;

// Do a request to the website and get the data jsonified.
function do_request() {
	return new Promise((success) => {
		fetch("https://echo.free.beeceptor.com/", {
			method: "POST",
			// doesn't work:
			//   headers: {
			//   	Origin: "https://echo.free.beeceptor.com/"
			//   }
		}).then((response) => {
			response.json().then((v) => {
				success(v);
			});
		});
	});
}

// Do a request to the website and get only the Origin header of its response.
function request_origin() {
	return new Promise((success) => {
		do_request().then((data) => {
			console.log(data);
			success(data.headers.Origin);
		});
	});
}

// Get the Origin from the website and send a notif.
function get_and_notify() {
	request_origin().then((origin) => {
		browser.notifications.create("", {
			type: "basic", message: typeof origin, title: String(origin)});
	});
}

setInterval(() => get_and_notify(), 10000);

Thanks for your help :sunny:

Ahh, okay. I now see that I got a bit tripped up by your terminology. Generally speaking the phrase “fetching a page” means “perform a GET request to retrieve the page” rather than “use the fetch() method to perform some kind of HTTP request.” Now that I see your code, it’s clear that using fetch() to execute a POST request, which does include the Origin header by default.

I was able to revise the code you shared to modify set the Origin header as desired using the Web Request API. Be aware that this will not work in Chrome because they dropped general support for the “webRequestBlocking” permission in Manifest V3.

Also, I took the liberty of converting the promise-based code you shared to async/await.

manifest.json

{
  "manifest_version": 3,
  "name": "Origin header tests",
  "version": "1.0",
  "background": {
    "scripts": ["background.js"]
  },
  "host_permissions": [
    "*://echo.free.beeceptor.com/*"
  ],
  "permissions": [
    "notifications",
    "webRequest",
    "webRequestBlocking"
  ]
}

background.js

// Do a request to the website and get the data jsonified.
async function do_request() {
  const request = await fetch("https://echo.free.beeceptor.com/", { method: "POST" });
  const response = await request.json();
  return response;
}

// Do a request to the website and get only the Origin header of its response.
async function request_origin() {
  const data = await do_request();
  console.log(data);
  return data.headers.Origin;
}

// Get the Origin from the website and send a notif.
async function get_and_notify() {
  try {
    const origin = await request_origin();
    browser.notifications.create({
      type: "basic",
      message: typeof origin,
      title: origin.toString(),
    });
  } catch (error) {
    console.error(error);
  }
}

browser.webRequest.onBeforeSendHeaders.addListener((request) => {
  const newValue = "echo.free.beeceptor.com";

  for (const header of request.requestHeaders) {
    if (header.name.toLowerCase() === "origin" && header.value.startsWith("moz-extension")) {
      header.value = newValue;
      break;
    }
  }
  return request;
}, {urls: ["*://echo.free.beeceptor.com/*"]}, ["blocking", "requestHeaders"]);

// Run immediately after installation to simplify testing
browser.runtime.onInstalled.addListener(() => get_and_notify());

// Re-run every 10 seconds
setInterval(() => get_and_notify(), 10 * 1000);