didof.dev

CORS, Richiesta di Preflight e Metodo OPTIONS

CORS, Richiesta di Preflight e Metodo OPTIONS

Scritto da Francesco Di Donato 10 aprile 2022 4 minuti di lettura

Share

Prima di tutto, devi essere a conoscenza della Politica di stessa origine 🔗. È un meccanismo integrato nel browser che limita come uno script su un’origine interagisce con una risorsa su un’altra.

Stessa origine 🏠🏠

Iniziamo con il caso più semplice. Sia il server che detiene la risorsa sia il client che la richiede risiedono sotto la stessa origine 🔗 (<scheme>://<hostname><port>).

Il server è esposto su http://localhost:3000 e risponde a HTTP GET / restituendo un file HTML.

Per brevità, gli esempi di codice mostrano solo ciò su cui voglio concentrarmi. Se qualcosa non è chiaro, usa i vari Codice sorgente accompagnatorio (stessa origine) 🔗 che troverai.

server.get("/", (_, reply) => {
  reply.sendFile("index.html");
});

server.get("/me", (_, reply) => {
  reply.status(200).send({ nickname: "didof" });
});

L’HTML arrivato sul client chiede al server sulla stessa origine la risorsa associata al percorso /me e mostra il soprannome sullo schermo.

<p>Soprannome: <output></output></p>

<script>
    // Questo gira su http://localhost:3000
    fetch("/me") // fetch("http://localhost:3000/me")
    .then((res) => res.json())
    .then((res) => {
        document.querySelector("output").textContent = res.nickname;
    });
</script>

Il payload è accessibile in quanto viene rispettata la politica di stessa origine. Non c’è bisogno di CORS, non c’è bisogno di richiesta di preflight.


Cross-origin 🏠🏭

Ora il client è esposto su http://localhost:8000 mentre il server con la risorsa è esposto su http://localhost:3000. Anche se schema e hostname sono gli stessi, le porte sono diverse - quindi sono due origini diverse.

Nel mondo reale, il seguente concetto si verifica quando il client su https://mywebsite.com richiede una risorsa a http://api.foo.it/bar


Codice sorgente accompagnatorio (cross-origin) 🔗

index.html

<script>
// Questo gira su http://localhost:8000
const request = new Request("http://localhost:3000/me", {
  method: "GET",
});

fetch(request)
.then((res) => res.json())
.then((res) => { ... })
</script>

Come puoi immaginare, hai un errore di CORS 🔗.

Accesso a fetch da 'http://localhost:3000/me' dall'origine 'http://localhost:8000' è stato bloccato dalla politica CORS: Nessun'intestazione 'Access-Control-Allow-Origin' è presente sulla risorsa richiesta. Se una risposta opaca soddisfa le tue esigenze, imposta la modalità della richiesta su 'no-cors' per recuperare la risorsa con CORS disabilitato. GET http://localhost:3000/me net::ERR_FAILED 200

L’errore nella console spiega chiaramente il problema. Correggiamolo. Sul server, gestiamo CORS.

server.register(fastifyCors, {
  origin: "http://localhost:8000",
});

Qui uso fastify 🔗, ma qualsiasi framework offre il proprio modo di gestire CORS. Sono semplicemente intestazioni aggiunte alla risposta, nient’altro - puoi naturalmente aggiungerle manualmente.

Ora, la risposta includerà l’access-control-allow-origin e il valore sarà http://localhost:8000.

access-control-allow-origin: http://localhost:8000

Il server sta fondamentalmente dando il permesso al browser che ha emesso la richiesta di aprire la risposta.


Richiesta di Preflight 🏠✉️🏭

Ci sono due tipi di richiesta CORS:

  • Richiesta semplice
  • Richiesta di preflight

Quale viene utilizzata è determinado dal browser. Fino a questo momento il client ha effettuato richieste semplici perché si adattano ai criteri 🔗.

Per lo scopo di questo post, devi solo sapere che per forzare il browser a effettuare una richiesta di preflight devi semplicemente aggiungere alla richiesta un intestazione personalizzata.

Codice sorgente accompagnatorio (cross-origin-preflight) 🔗

<script>
const headers = new Headers();
headers.append("X-CUSTOM-HEADER", "foo");

// Questo gira su http://localhost:8000
const request = new Request("http://localhost:3000/me", {
  method: "GET",
  headers,
});

fetch(request)
  .then((res) => res.json())
  .then((res) => { ... })

E questo è sufficiente perché il browser lanci due richieste invece di una.

richiesta 1 = localhost, richiesta 2 = me (preflight), richiesta 3 = me (richiesta effettiva)

Il metodo utilizzato è OPTIONS 🔗, che è interpretato dal server come una richiesta di informazioni sul request url definito.

Il browser aggiunge anche alcune intestazioni alla richiesta di preflight. Access-Control-Request-Headers e Access-Control-Request-Method con i loro valori relativi. Access-Control-Request-Headers: x-custom-header, Access-Control-Request-Method: GET

Il browser sta chiedendo permesso al server per effettuare una richiesta GET che ha tra le varie intestazioni anche un certo X-CUSTOM-HEADER.

Questo particolare server, configurato per essere estremamente permissivo, risponde con access-control-allow-headers il cui valore rispecchia quello richiesto e access-control-allow-methods che dice che tutti i metodi sono consentiti.

access-control-allow-headers: x-custom-header, access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE

Sta dando un grande pollice in su 👍. Il browser può quindi eseguire la richiesta desiderata.

Ovviamente, se per qualche motivo il server fosse istruito a non consentire la presenza dell’intestazione personalizzata, riceveresti un errore.

Accesso a fetch da 'http://localhost:3000/me' dall'origine 'http://localhost:8000' è stato bloccato dalla politica CORS: il campo dell'intestazione di richiesta x-custom-header non è consentito da Access-Control-Allow-Headers nella risposta di preflight.


Come detto, questo è solo una semplice chiacchierata. Se sei interessato, di seguito ci sono alcuni riferimenti per approfondire. Io 💖 l’H di HTML!

Risorse 🕳️🐇