¿Cómo realizar una solicitud de origen cruzado en un script de contenido (actualmente bloqueado por CORB a pesar de los encabezados CORS correctos)?
Estoy desarrollando una extensión de Chrome que realiza solicitudes desde ciertos sitios web a una API que controlo. Hasta Chrome 73, la extensión funcionaba correctamente. Después de actualizar a Chrome 73, comencé a recibir el siguiente error:
El bloqueo de lectura de origen cruzado (CORB) bloqueó la respuesta de origen cruzado http://localhost:3000/api/users/1 con aplicación/json de tipo MIME
Según la documentación de Chrome sobre CORB , CORB bloqueará la respuesta de una solicitud si se cumple todo lo siguiente:
El recurso es un "recurso de datos". Específicamente, el tipo de contenido es HTML, XML, JSON.
El servidor responde con un
X-Content-Type-Options: nosniff
encabezado o, si este encabezado se omite, Chrome detecta que el tipo de contenido es HTML, XML o JSON al inspeccionar el archivo.CORS no permite explícitamente el acceso al recurso
Además, según "Lessons from Spectre and Meltdown" (Google I/O 2018) , parece que puede ser importante agregar mode: cors
invocaciones fetch
, es decir, fetch(url, { mode: 'cors' })
.
Para intentar solucionar este problema, hice los siguientes cambios:
Primero, agregué los siguientes encabezados a todas las respuestas de mi 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
En segundo lugar, actualicé mi fetch()
invocación en la extensión para que se vea así:
fetch(url, { credentials: 'include', mode: 'cors' })
Sin embargo, estos cambios no funcionaron. ¿Qué puedo cambiar para que CORB no bloquee mi solicitud?
Según los ejemplos de "Cambios en solicitudes de origen cruzado en scripts de contenido de extensión de Chrome" , reemplacé todas las invocaciones de fetch
con un nuevo método fetchResource
, que tiene una API similar, pero delega la fetch
llamada a la página en segundo plano:
// 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;
});
Este es el conjunto de cambios más pequeño que pude realizar en mi aplicación y que soluciona el problema. (Tenga en cuenta que las extensiones y las páginas en segundo plano solo pueden pasar objetos serializables JSON entre ellas, por lo que no podemos simplemente pasar el objeto Fetch API Response de la página en segundo plano a la extensión).
Las páginas en segundo plano no se ven afectadas por CORS o CORB, por lo que el navegador ya no bloquea las respuestas de la API.
¡Advertencia! La URL debe agregarse en manifest.json :
- Para ManifestV3 en
"host_permissions": ["*://*.example.com/"]
- Para ManifestV2 en
"permissions": ["*://*.example.com/"]
Consulte https://www.chromium.org/Home/chromium-security/extension-content-script-fetches
Para mejorar la seguridad, las recuperaciones de orígenes cruzados de secuencias de comandos de contenido no están permitidas en las extensiones de Chrome desde Chrome 85. En su lugar, dichas solicitudes se pueden realizar desde la secuencia de comandos en segundo plano de la extensión y se retransmiten a las secuencias de comandos de contenido cuando sea necesario.
Puedes hacer eso para evitar el origen cruzado.
Script de contenido antiguo, realizando una búsqueda de origen cruzado:
var itemId = 12345;
var url = "https://another-site.com/price-query?itemId=" +
encodeURIComponent(request.itemId);
fetch(url)
.then(response => response.text())
.then(text => parsePrice(text))
.then(price => ...)
.catch(error => ...)
Nuevo script de contenido, que solicita a su página de fondo que recupere los datos:
chrome.runtime.sendMessage(
{contentScriptQuery: "queryPrice", itemId: 12345},
price => ...);
Nueva página de fondo de extensión, que se obtiene de una URL conocida y transmite datos:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.contentScriptQuery == "queryPrice") {
var url = "https://another-site.com/price-query?itemId=" +
encodeURIComponent(request.itemId);
fetch(url)
.then(response => response.text())
.then(text => parsePrice(text))
.then(price => sendResponse(price))
.catch(error => ...)
return true; // Will respond asynchronously.
}
});
Permitir la URL en manifest.json ( más información ):
- ManifiestoV2 (clásico):
"permissions": ["https://another-site.com/"]
- ManifestV3 ( próximo ):
"host_permissions": ["https://another-site.com/"]