Un errore CORS ("No 'Access-Control-Allow-Origin' header is present") significa che una pagina web su un'origine, come http://localhost:5173, ha chiamato la tua API su un'altra origine e il browser non ha lasciato leggere la risposta alla pagina, perché la risposta non diceva che quell'origine era autorizzata. La soluzione sta sul server: invia Access-Control-Allow-Origin con l'origine della pagina e rispondi alla richiesta preflight OPTIONS del browser con i metodi e gli header che accetti. In Express sono poche righe di middleware, oppure app.use(cors()) dal pacchetto cors.
La risposta è la lista dei prodotti, e la sua scheda Headers mostra access-control-allow-origin: https://app.example.com: una pagina su quell'origine può leggerla. Ora invia OPTIONS /api/products/1 con gli header Origin: https://app.example.com e Access-Control-Request-Method: PUT. È il preflight che un browser invia prima di un PUT, e la risposta è 204 con i metodi e gli header consentiti e nessun body.
Cosa controlla il browser
Le pagine della stessa origine non hanno bisogno di niente. Per una richiesta cross-origin il browser legge questi header della risposta:
| Header | Significato |
|---|---|
Access-Control-Allow-Origin | L'unica origine che può leggere la risposta, oppure * per tutte |
Access-Control-Allow-Methods | I metodi consentiti nella richiesta vera (solo preflight) |
Access-Control-Allow-Headers | Gli header che la pagina può inviare, come Authorization (solo preflight) |
Access-Control-Allow-Credentials | true se la pagina può inviare cookie e leggere la risposta |
Access-Control-Max-Age | Per quanti secondi il browser può tenere in cache la risposta al preflight |
Vary: Origin | Dice alle cache che la risposta dipende dall'header Origin della richiesta |
Una semplice GET o una POST in stile form arriva direttamente al server, e il browser controlla Access-Control-Allow-Origin nella risposta. Il server esegue comunque la richiesta; CORS decide solo se la pagina vede il risultato. Una richiesta con un body JSON, un header Authorization o un metodo come PUT o DELETE è preceduta da una richiesta preflight OPTIONS, e quella vera parte solo se la risposta al preflight lo consente. Per questo un middleware CORS deve rispondere a OPTIONS prima di qualsiasi route o controllo di autenticazione.
Consentire più origini, con le credentials
Access-Control-Allow-Origin contiene una sola origine o *, mai una lista. Per consentirne più di una, confronta l'header Origin della richiesta con una lista di origini ammesse e rimandalo indietro quando corrisponde:
La prima richiesta non ha l'header Origin, come una chiamata da curl o da una pagina della stessa origine, quindi riceve {"origin":null} e nessun header CORS. Invia GET /api/me con l'header Origin: http://localhost:5173 e la scheda Headers mostra quell'origine rimandata indietro più access-control-allow-credentials: true. Cambia l'header in Origin: https://evil.example e i due header CORS spariscono: il server risponde comunque, ma un browser impedirebbe alla pagina di leggere la risposta. Access-Control-Allow-Credentials: true permette alla pagina di inviare cookie (con fetch(url, { credentials: 'include' })), e i browser lo rifiutano insieme a *, quindi le credentials richiedono sempre l'origine esatta.
Il pacchetto cors
Il pacchetto cors fa lo stesso lavoro tramite opzioni. Installalo con npm install cors:
const cors = require('cors');
// Any origin, no cookies: fine for a public read-only API.
app.use(cors());
// Only your frontend, with cookies.
app.use(cors({
origin: ['https://app.example.com', 'http://localhost:5173'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
// One route only.
app.get('/api/public', cors(), handler);
Chiamato senza opzioni, cors() invia Access-Control-Allow-Origin: * e risponde a ogni preflight. Usa * solo per dati pubblici che non richiedono login.
Errori comuni
Aggiungere CORS dopo le route o dopo un middleware di autenticazione. La richiesta preflight OPTIONS non ha l'header Authorization, quindi un controllo di autenticazione messo prima risponde 401 e il browser segnala un errore CORS. Registra per primo il middleware CORS.
Usare * con le credentials. I browser rifiutano la risposta. Rimanda invece l'origine specifica.
Provare a risolvere CORS nel frontend. Opzioni della richiesta come mode: 'no-cors' non tolgono la restrizione; ti danno una risposta opaca che non puoi leggere. Solo gli header del server risolvono il problema.
Per come funzionano app.use() e l'ordine dei middleware, vedi i middleware di Express.
Domande frequenti
Perché la mia API funziona in Postman o curl ma non nel browser?
CORS viene applicato solo dai browser. Postman, curl e gli altri server ignorano gli header CORS, quindi leggono la risposta in ogni caso. Il browser si rifiuta di passare la risposta agli script di una pagina con un'altra origine, a meno che il server non autorizzi quell'origine.
CORS è una protezione per il mio server?
Protegge gli utenti, non il tuo server. Impedisce a una pagina malevola di leggere le risposte della tua API con i cookie dell'utente. Chiunque può comunque chiamare la tua API da uno script o da un server, quindi autenticazione e autorizzazione restano compito tuo.
Cosa conta come origine diversa?
Un'origine è schema, host e porta insieme. http://localhost:5173 e http://localhost:3000 sono origini diverse perché cambia la porta, e https://example.com e https://api.example.com cambiano per l'host.
Posso evitare CORS in sviluppo?
Sì, rendendo la richiesta same-origin: configura il dev server del frontend (per esempio server.proxy di Vite) perché inoltri /api al server Express. Così il browser parla con una sola origine e non servono header CORS.