JavaScript Fetch: Richieste cross-origin (CORS)
Impara a fare richieste cross-origin con la Fetch API di JavaScript: come funziona CORS, le opzioni mode e credentials, le richieste preflight e le best practice per la gestione degli errori.
Le richieste cross-origin consentono a una pagina web di caricare dati da un server su un'origine diversa rispetto alla pagina stessa. Alimentano quasi ogni applicazione moderna: chiamare un'API pubblica, comunicare con il proprio backend su un altro sottodominio o incorporare un servizio di terze parti. Questa pagina spiega come funzionano le regole CORS del browser, come effettuare chiamate cross-origin con la Fetch API e come gestire correttamente credenziali, richieste preflight ed errori.
Cosa si intende per "cross-origin"
Un'origine è la combinazione di protocollo + host + porta. Due URL condividono la stessa origine solo quando tutti e tre questi elementi corrispondono. Se anche uno solo è diverso, la richiesta tra di essi è cross-origin.
Richiesta da https://app.example.com a… | Stessa origine? | Motivo |
|---|---|---|
https://app.example.com/api/users | Sì | protocollo, host e porta identici |
http://app.example.com/api | No | protocollo diverso (http vs https) |
https://api.example.com/users | No | host diverso (anche il sottodominio conta) |
https://app.example.com:8443/api | No | porta diversa |
Le richieste same-origin sono senza restrizioni. Le richieste cross-origin sono regolate da CORS.
Cos'è CORS (e cosa non è)
Cross-Origin Resource Sharing (CORS) è un meccanismo di sicurezza del browser che stabilisce se JavaScript su un'origine è autorizzato a leggere una risposta proveniente da un'altra origine. La decisione è presa dal server, che aderisce inviando specifici header di risposta HTTP. Il browser li applica poi in modo coercitivo.
Due cose sono facili da confondere:
- CORS non è qualcosa che si risolve esclusivamente nel codice front-end. Se il server non invia gli header corretti, nessuna opzione di
fetchfarà sì che la lettura abbia successo. - La richiesta spesso raggiunge comunque il server ed è elaborata lì; CORS blocca solo la possibilità dello script di leggere la risposta. Ecco perché CORS non è un sostituto dell'autenticazione.
Effettuare una richiesta cross-origin con Fetch
La Fetch API è il metodo moderno e basato su promise per effettuare richieste HTTP. Una semplice chiamata fetch verso un'altra origine è già cross-origin: il browser gestisce CORS automaticamente.
Questo funziona perché jsonplaceholder.typicode.com restituisce Access-Control-Allow-Origin: *. Se non lo facesse, il browser bloccherebbe la lettura e fetch verrebbe rifiutato. Consulta il capitolo sulla Fetch API per il modello completo di richiesta/risposta.
Gli header del server che rendono tutto possibile
Quando si effettua una richiesta cross-origin, il server deve includere gli header CORS per consentirla. Quelli più comuni:
Access-Control-Allow-Origin— quale/quali origine/i può leggere la risposta (un'origine specifica oppure*per qualsiasi).Access-Control-Allow-Methods— quali metodi HTTP sono consentiti (usato nelle risposte preflight).Access-Control-Allow-Headers— quali header di richiesta il client può inviare (usato nelle risposte preflight).Access-Control-Allow-Credentials— se cookie/autenticazione possono essere inviati (deve esseretrueper consentire le credenziali).
Questi si configurano sul server, non in fetch. Il front end controlla solo la richiesta.
L'opzione mode
L'opzione mode di Fetch dichiara come deve comportarsi la gestione cross-origin:
'cors'— il valore predefinito. La richiesta è consentita solo se il server restituisce gli header CORS corrispondenti; altrimenti la lettura viene bloccata.'same-origin'— fallisce immediatamente per qualsiasi URL cross-origin.'no-cors'— invia la richiesta ma restituisce una risposta opaca: non è possibile leggere il suo status, gli header o il body. Utile solo per casi fire-and-forget come il caching di un'immagine in un service worker.
Poiché 'cors' è già il valore predefinito, raramente è necessario impostarlo, ma essere espliciti documenta l'intento:
Sul server, evitare Access-Control-Allow-Origin: * in produzione. Consentire solo le origini specifiche di cui ci si fida.
Invio delle credenziali
Per impostazione predefinita, le richieste fetch cross-origin non inviano cookie o header HTTP-auth. Per includerli, impostare l'opzione credentials su 'include'. Il server deve rispondere anche con Access-Control-Allow-Credentials: true e indicare la propria origine esatta in Access-Control-Allow-Origin — * viene rifiutato quando sono coinvolte le credenziali. Consulta Cookie e document.cookie per capire come si comportano i cookie tra siti diversi.
async function fetchWithCredentials(url) {
try {
const response = await fetch(url, {
mode: 'cors',
credentials: 'include'
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Fetch error:', error);
}
}
fetchWithCredentials('https://api.crossorigin.com/secure-data');Quando una policy CORS blocca la lettura cross-origin, il browser non consegna la risposta allo script. Al contrario, fetch viene rifiutato con un TypeError ("Failed to fetch"), quindi l'errore finisce nel blocco catch — non nel controllo if (!response.ok).
Richieste preflight
Per le richieste che il browser considera potenzialmente non sicure, viene inviata automaticamente una richiesta OPTIONS — un preflight — per chiedere al server se la richiesta reale è consentita. Il codice non emette mai questa chiamata OPTIONS; è il browser a farlo al posto tuo.
Una richiesta attiva un preflight quando non è una "richiesta semplice", ovvero quando:
- utilizza un metodo diverso da
GET,HEADoPOST; - include header di richiesta personalizzati (ad esempio
AuthorizationoX-Api-Key); - utilizza un
Content-Typediverso daapplication/x-www-form-urlencoded,multipart/form-dataotext/plain(inviare JSON conapplication/jsonè il trigger più comune).
Un tipico POST JSON richiede quindi due round trip: il preflight e poi la richiesta reale:
async function createPost(url, payload) {
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }, // triggers a preflight
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Fetch error:', error);
}
}
createPost('https://api.example.com/posts', { title: 'Hello' });Affinché il preflight abbia successo, il server deve rispondere alla richiesta OPTIONS con Access-Control-Allow-Methods e Access-Control-Allow-Headers che coprano ciò che la richiesta reale utilizzerà. Se fallisce, il browser genera un errore di rete e la richiesta reale non viene mai inviata.
Distinguere le modalità di errore
Gli errori CORS sono notoriamente confusi perché il browser nasconde i dettagli per ragioni di sicurezza. Usa questa checklist:
fetchviene rifiutato ("Failed to fetch") — di solito si tratta di un blocco CORS, un errore di rete o un preflight fallito. Controlla la console del browser per il messaggio CORS specifico; non è esposto a JavaScript.response.okèfalse— la richiesta è andata a buon fine e CORS è passato, ma il server ha restituito uno status 4xx/5xx. Si tratta di un errore applicativo, non CORS.- I cookie non vengono inviati — hai dimenticato
credentials: 'include', oppure il server non sta restituendoAccess-Control-Allow-Credentials: truecon un'origine specifica.
Racchiudi le chiamate in try/catch e controlla response.ok separatamente, come negli esempi sopra.
Best practice
- Limita
Access-Control-Allow-Origin. Elenca le origini esatte di cui ti fidi invece di*, specialmente quando sono coinvolte le credenziali (*viene rifiutato con le credenziali). - Usa sempre HTTPS. Protegge i dati in transito ed è richiesto da molte API per contesti sicuri.
- Riduci i preflight. Evita header personalizzati non necessari e fai in modo che il server invii
Access-Control-Max-Ageaffinché il browser memorizzi nella cache i risultati dei preflight. - Gestisci gli errori con eleganza. Separa i fallimenti di trasporto/CORS (
catch) dagli errori di status HTTP (!response.ok) e mostra messaggi utili all'utente. - Non affidarti a CORS per la sicurezza. Controlla ciò che i browser consentono agli script di leggere; non autentica il chiamante. Valida e autorizza ogni richiesta lato server.
Capitoli correlati
- Fetch API — la base per ogni richiesta in questa pagina.
- Annullare un fetch — annulla richieste lente o indesiderate.
- Lavorare con JSON — analizza e serializza i body di richiesta e risposta.
- Cookie: document.cookie — come i cookie interagiscono con le credenziali cross-origin.
Riepilogo
Una richiesta è cross-origin quando il suo protocollo, host o porta differisce da quelli della pagina. CORS consente al server di decidere quali origini possono leggere la risposta, e il browser applica tale decisione. Con Fetch, la modalità 'cors' è quella predefinita; usa l'opzione credentials per inviare cookie, aspettati una richiesta preflight OPTIONS per le chiamate non semplici e gestisci i fallimenti CORS in catch mentre controlli response.ok per gli errori a livello HTTP.