Torna al blog

CORS: Cos'è e Come Funziona la Cross-Origin Resource Sharing

Guida completa a CORS (Cross-Origin Resource Sharing): come funziona questa politica di sicurezza del browser, perché è importante e come implementarla correttamente nelle tue applicazioni web.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

14 min di lettura
CORS: Cos'è e Come Funziona la Cross-Origin Resource Sharing

Introduzione

Nello sviluppo di applicazioni web moderne, la comunicazione tra domini diversi è diventata una necessità quasi quotidiana. Le Single Page Application (SPA) che chiamano API esterne, i servizi di terze parti, le cdn per le risorse e le architetture distribuite sono ormai onnipresenti nell'ecosistema web. Tuttavia, questa comunicazione cross-origin si scontra con uno dei principi fondamentali di sicurezza del web: la Same-Origin Policy.

Per bilanciare le esigenze di sicurezza con la necessità di comunicazione tra domini diversi, i browser hanno implementato un meccanismo chiamato Cross-Origin Resource Sharing, o CORS. Questo sistema consente ai server di dichiarare esplicitamente quali origini sono autorizzate ad accedere alle loro risorse, fornendo un modo controllato e sicuro per aggirare le restrizioni della Same-Origin Policy.

CORS è spesso fonte di frustrazione per molti sviluppatori. Gli errori relativi alle politiche CORS sono tra i problemi più comuni durante lo sviluppo di applicazioni web moderne, e la loro risoluzione richiede una comprensione chiara di come funziona questo meccanismo e del perché esiste in primo luogo.

In questo articolo, esploreremo a fondo cosa sia CORS, come funzioni a livello di browser e server, perché sia fondamentale per la sicurezza del web e quali siano le best practice per implementarlo correttamente. Che tu stia sviluppando un'API, una SPA o semplicemente cercando di capire perché il tuo frontend non riesce a comunicare con un servizio esterno, questa guida ti fornirà le conoscenze necessarie per navigare con sicurezza nel mondo delle richieste cross-origin.

Cos'è CORS

Cross-Origin Resource Sharing (CORS) è un meccanismo di sicurezza implementato dai browser web che consente a risorse web su un dominio di richiedere risorse da un dominio diverso da quello da cui è stata servita la prima risorsa. È fondamentalmente un sistema che permette di allentare in modo controllato le restrizioni imposte dalla Same-Origin Policy.

Same-Origin Policy: Il fondamento della sicurezza web

Per comprendere CORS, dobbiamo prima capire la politica che lo ha reso necessario: la Same-Origin Policy (SOP).

La Same-Origin Policy è una delle misure di sicurezza più importanti implementate nei browser web. Questa politica impedisce a documenti o script caricati da un'origine di accedere o interagire con risorse provenienti da un'altra origine. Un'origine è definita dalla combinazione di:

  • Protocollo (http, https)
  • Host (dominio o indirizzo IP)
  • Porta (80, 443, ecc.)

Ad esempio, se un documento caricato da https://example.com tenta di fare una richiesta XMLHttpRequest o Fetch API a https://api.different-domain.com, tale richiesta verrà bloccata dalla Same-Origin Policy.

Questa restrizione è cruciale per la sicurezza: impedisce a siti malevoli di leggere dati sensibili da altri siti su cui l'utente potrebbe essere autenticato. Senza SOP, un sito dannoso potrebbe, ad esempio, eseguire script che leggono la vostra webmail o accedono ai vostri dati bancari se siete loggati in un'altra scheda.

CORS: L'evoluzione necessaria

Con l'evoluzione del web verso applicazioni sempre più distribuite, la rigida applicazione della Same-Origin Policy è diventata un ostacolo significativo. Considerando scenari come:

  • Frontend e backend ospitati su domini diversi
  • API pubbliche accessibili da client di terze parti
  • Servizi di microservizi comunicanti tra loro
  • CDN che servono risorse su domini diversi

In queste situazioni, il modello di sicurezza doveva evolversi per consentire interazioni legittime tra origini diverse, pur mantenendo le protezioni essenziali. CORS è stata la risposta a questa esigenza.

CORS funziona estendendo HTTP con nuove intestazioni che permettono ai server di descrivere l'insieme di origini autorizzate a leggere informazioni tramite un browser web. Inoltre, per richieste HTTP che potrebbero avere conseguenze sul server (come richieste non GET o con certi tipi di header), la specifica richiede che i browser "preflight" la richiesta, richiedendo al server quali metodi e header sono permessi tramite una richiesta HTTP OPTIONS, e poi inviando la richiesta effettiva solo se consentita.

Dal punto di vista della sicurezza

È importante notare che CORS non è un meccanismo per proteggere il server dalle richieste indesiderate, ma è esclusivamente una protezione lato client implementata dai browser. Le restrizioni CORS si applicano solo alle richieste effettuate da un browser; altri tipi di client HTTP (come curl, Postman o script server-side) possono comunque effettuare richieste cross-origin senza rispettare CORS.

CORS dovrebbe essere visto come una "salvaguardia per l'utente", non per il server. I server devono comunque implementare le proprie misure di sicurezza (autenticazione, autorizzazione, validazione input, ecc.) indipendentemente da CORS.

Come funziona CORS

Il funzionamento di CORS si basa su un sistema di intestazioni HTTP che vengono scambiate tra browser e server. Queste intestazioni permettono al server di specificare quali origini hanno il permesso di accedere alle sue risorse e come.

Le intestazioni CORS principali

Ecco le intestazioni HTTP più importanti utilizzate in CORS:

Intestazioni di risposta (dal server al browser)

  • Access-Control-Allow-Origin: Specifica quali origini possono accedere alla risorsa. Può essere un singolo origine, * (tutte le origini), o null.
  • Access-Control-Allow-Methods: Indica i metodi HTTP (GET, POST, PUT, DELETE, ecc.) consentiti quando si accede alla risorsa.
  • Access-Control-Allow-Headers: Specifica quali intestazioni HTTP possono essere utilizzate durante la richiesta effettiva.
  • Access-Control-Allow-Credentials: Indica se la risposta può essere condivisa quando la richiesta include credenziali (cookie, header di autorizzazione, ecc.).
  • Access-Control-Expose-Headers: Elenca le intestazioni che i browser sono autorizzati a accedere.
  • Access-Control-Max-Age: Specifica per quanto tempo (in secondi) i risultati di una richiesta preflight possono essere memorizzati nella cache.

Intestazioni di richiesta (dal browser al server)

  • Origin: Indica l'origine della richiesta (combinazione di protocollo, dominio e porta).
  • Access-Control-Request-Method: Utilizzata nelle richieste preflight per indicare quale metodo HTTP verrà utilizzato nella richiesta effettiva.
  • Access-Control-Request-Headers: Utilizzata nelle richieste preflight per indicare quali intestazioni HTTP verranno utilizzate nella richiesta effettiva.

Il flusso CORS: Richieste semplici vs preflight

CORS gestisce due tipi di richieste cross-origin in modo diverso: richieste semplici e richieste che richiedono preflight.

Richieste semplici

Una richiesta è considerata "semplice" se soddisfa tutte queste condizioni:

  1. Utilizza uno dei seguenti metodi: GET, HEAD o POST
  2. Imposta solo intestazioni automaticamente impostate dal browser (come Connection, User-Agent) o le seguenti: Accept, Accept-Language, Content-Language, Content-Type
  3. Se presente, Content-Type ha uno dei seguenti valori: application/x-www-form-urlencoded, multipart/form-data, o text/plain
  4. Non utilizza oggetti ReadableStream nella richiesta
  5. Non utilizza event listener nel caricamento upload/download

Per le richieste semplici, il browser invia:

  • L'intestazione Origin con l'origine del documento corrente

Il server risponde con:

  • Access-Control-Allow-Origin contenente l'origine consentita o *
  • Opzionalmente, altre intestazioni CORS come Access-Control-Allow-Credentials

Il browser quindi consente o blocca la risposta in base a queste intestazioni.

Richieste con preflight

Per richieste più complesse (che utilizzano altri metodi HTTP come PUT/DELETE, impostano intestazioni personalizzate o utilizzano Content-Type diversi), il browser invia prima una richiesta "preflight" utilizzando il metodo OPTIONS.

Il flusso è il seguente:

  1. Richiesta preflight: Il browser invia una richiesta OPTIONS con le intestazioni:

    • Origin
    • Access-Control-Request-Method (il metodo della richiesta effettiva)
    • Access-Control-Request-Headers (le intestazioni della richiesta effettiva)
  2. Risposta preflight: Il server risponde con:

    • Access-Control-Allow-Origin
    • Access-Control-Allow-Methods
    • Access-Control-Allow-Headers
    • Access-Control-Max-Age (opzionale)
    • Access-Control-Allow-Credentials (opzionale)
  3. Se il server approva la richiesta (le origini, i metodi e le intestazioni sono consentiti), il browser procede con la richiesta effettiva. Altrimenti, il browser blocca la richiesta e genera un errore CORS.

Esempio di flusso CORS con preflight

Ecco come appare un tipico scambio CORS per una richiesta che richiede preflight:

1. Richiesta preflight (OPTIONS):

OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.org
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization

2. Risposta preflight:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.org
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

3. Richiesta effettiva (se preflight ha successo):

PUT /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.org
Content-Type: application/json
Authorization: Bearer token123
{...}

4. Risposta effettiva:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.org
Content-Type: application/json
{...}

CORS con credenziali

Di default, le richieste cross-origin non includono credenziali come cookie, token di autorizzazione o certificati client TLS. Se hai bisogno di inviare credenziali nelle richieste cross-origin, devi:

  1. Lato client: Impostare l'opzione credentials: 'include' nella richiesta Fetch o withCredentials: true per XMLHttpRequest

  2. Lato server: Impostare Access-Control-Allow-Credentials: true nella risposta

È importante notare che quando si utilizzano credenziali, il server non può utilizzare il wildcard * nell'intestazione Access-Control-Allow-Origin ma deve specificare esplicitamente l'origine consentita.

Perché CORS è importante

CORS svolge un ruolo cruciale nell'ecosistema web moderno, bilanciando sicurezza e funzionalità. Comprendere la sua importanza ci aiuta a implementarlo correttamente e a rispettare i suoi principi.

Sicurezza del Web

CORS è fondamentale per la sicurezza del web per diversi motivi:

  1. Prevenzione degli attacchi CSRF (Cross-Site Request Forgery): Limita la capacità di siti malevoli di effettuare richieste non autorizzate a servizi su cui l'utente è autenticato.

  2. Protezione dei dati sensibili: Impedisce a script di terze parti di accedere a informazioni private su altri domini.

  3. Controllo granulare dell'accesso: Permette ai server di specificare esattamente quali origini, metodi e intestazioni sono consentiti, limitando la superficie di attacco.

  4. Trasparenza: Rende esplicite le intenzioni del server riguardo l'accesso cross-origin, migliorando la comprensione del modello di sicurezza.

Abilitazione di architetture web moderne

Oltre alla sicurezza, CORS è essenziale per consentire molti pattern architetturali moderni:

  1. Separazione front-end/back-end: Consente di ospitare l'interfaccia utente e i servizi API su domini diversi, una pratica comune nello sviluppo moderno.

  2. Microservizi e API pubbliche: Permette l'accesso a più servizi da una singola applicazione client.

  3. Content Delivery Networks (CDN): Facilita il caricamento di risorse da reti di distribuzione di contenuti su domini diversi.

  4. Integrazione di servizi di terze parti: Consente l'integrazione con API esterne come servizi di pagamento, mappe, social media, ecc.

  5. Sviluppo multipiattaforma: Supporta applicazioni che funzionano su web, mobile e desktop accedendo alle stesse API.

Le conseguenze di una configurazione errata

Una configurazione CORS errata può avere conseguenze significative:

  1. Configurazione troppo restrittiva: Può impedire legittime richieste cross-origin, causando malfunzionamenti dell'applicazione e frustrando gli utenti.

  2. Configurazione troppo permissiva: Può compromettere la sicurezza, permettendo a origini non intenzionali di accedere a dati sensibili.

  3. Perdita di credenziali: L'errata configurazione con credenziali può esporre informazioni sensibili dell'utente.

  4. Difficoltà di debug: Gli errori CORS possono essere difficili da diagnosticare, specialmente per sviluppatori meno esperti.

  5. Problemi in produzione: Configurazioni che funzionano in sviluppo potrebbero fallire in produzione a causa di domini, porte o protocolli diversi.

Best Practice per l'implementazione di CORS

Implementare CORS correttamente richiede un attento bilanciamento tra sicurezza e funzionalità. Ecco le best practice da seguire:

Principio del privilegio minimo

  1. Limitare le origini consentite: Invece di utilizzare il wildcard *, specificare esplicitamente le origini che necessitano di accesso.

  2. Limitare i metodi HTTP: Consentire solo i metodi necessari per il funzionamento dell'applicazione.

  3. Limitare le intestazioni: Specificare solo le intestazioni richieste dalle tue applicazioni client.

  4. Evitare di consentire credenziali a meno che non sia necessario: L'utilizzo di Access-Control-Allow-Credentials: true aumenta il rischio di sicurezza e dovrebbe essere limitato ai casi d'uso necessari.

Configurazione sicura

  1. Validare attentamente l'origine: Verificare che l'origine sia in una lista consentita, evitando verifiche basate su substring che potrebbero essere aggirate.

  2. Utilizzare HTTPS: Configurare CORS solo per origini HTTPS in ambienti di produzione.

  3. Impostare Access-Control-Max-Age appropriatamente: Un valore troppo alto ritarda l'applicazione di modifiche alla policy, uno troppo basso aumenta il carico sul server.

  4. Considerare diversi ambienti: Configurazioni CORS diverse per sviluppo, staging e produzione.

Implementazione di base

Un'implementazione di base di CORS su un server può essere simile a questa:

// Pseudocodice per l'implementazione CORS in un middleware server
function corsMiddleware(req, res, next) {
  // Lista origini consentite
  const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];

  // Ottieni l'origine dalla richiesta
  const origin = req.headers.origin;

  // Verifica se l'origine è nella lista consentita
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    res.setHeader('Access-Control-Max-Age', '86400'); // 24 ore

    // Se è una richiesta preflight, termina qui
    if (req.method === 'OPTIONS') {
      return res.status(204).end();
    }
  }

  next();
}

Questa è una semplificazione; in situazioni reali, considera l'utilizzo di librerie CORS testate e mantenute per il tuo specifico stack tecnologico.

Casi d'uso comuni di CORS

CORS è fondamentale in molti scenari di sviluppo web moderno. Vediamo alcuni dei casi d'uso più comuni.

API RESTful e frontend separati

Uno degli scenari più comuni è avere un'API RESTful separata dall'applicazione frontend:

  • Scenario: Un'applicazione React/Vue/Angular servita da app.example.com che comunica con un'API su api.example.com
  • Configurazione CORS: L'API deve consentire richieste dall'origine https://app.example.com
  • Considerazioni: Se l'API deve supportare più applicazioni client, ciascuna origine deve essere consentita esplicitamente

API pubbliche

Le API destinate all'uso pubblico richiedono configurazioni CORS più permissive:

  • Scenario: Un'API meteo, una libreria di mappe, o un gateway di pagamento usato da molti siti
  • Configurazione CORS: Spesso utilizza Access-Control-Allow-Origin: * per consentire l'accesso da qualsiasi origine
  • Considerazioni: Le API pubbliche che richiedono autenticazione non possono utilizzare il wildcard con credenziali

Microservizi

Le architetture a microservizi spesso coinvolgono comunicazioni cross-origin:

  • Scenario: Un'applicazione frontend che comunica con diversi microservizi (autenticazione, prodotti, pagamenti, ecc.), ciascuno con il proprio dominio
  • Configurazione CORS: Ogni microservizio deve consentire richieste dall'origine del frontend
  • Considerazioni: La gestione di token JWT o altri meccanismi di autenticazione attraverso richieste CORS richiede attenzione particolare

Contenuti multimediali e CDN

Il caricamento di risorse da CDN è un caso d'uso comune per CORS:

  • Scenario: Caricamento di font, immagini o video da un dominio CDN diverso dal sito principale
  • Configurazione CORS: Il CDN deve consentire richieste dal dominio principale
  • Considerazioni: Particolarmente importante per font web e richieste di risorse con XMLHttpRequest o Fetch

Ambiente di sviluppo

Durante lo sviluppo, spesso si lavora con server locali su porte diverse:

  • Scenario: Un frontend in esecuzione su localhost:3000 che comunica con un backend su localhost:8080
  • Configurazione CORS: Il backend di sviluppo spesso consente richieste da tutte le origini o specificamente da http://localhost:3000
  • Considerazioni: Importante assicurarsi che le configurazioni di sviluppo non vengano trasferite in produzione

Conclusione

Cross-Origin Resource Sharing (CORS) rappresenta un elemento cruciale nell'architettura di sicurezza del web moderno, bilanciando la necessità di protezione con l'esigenza di comunicazione tra domini diversi. Non è semplicemente un ostacolo tecnico da superare, ma un meccanismo di sicurezza deliberato che gioca un ruolo fondamentale nella protezione degli utenti.

La comprensione approfondita di CORS è essenziale per qualsiasi sviluppatore web. Abbiamo visto come CORS estenda la Same-Origin Policy consentendo comunicazioni cross-origin controllate, come funzioni attraverso intestazioni HTTP specifiche, e come distingua tra richieste semplici e quelle che richiedono preflight. Abbiamo esplorato l'importanza di CORS sia per la sicurezza che per l'abilitazione di architetture web moderne, e abbiamo delineato le best practice per implementarlo correttamente.

CORS rappresenta un perfetto esempio di come il web evolva per bilanciare innovazione e sicurezza. La sua esistenza ci ricorda che la sicurezza nel web è un processo continuo di adattamento e miglioramento, non un problema risolto una volta per tutte.

Implementato correttamente, CORS consente di costruire applicazioni moderne, distribuite e sicure. Ignorarlo o implementarlo erroneamente può portare a vulnerabilità o a frustranti problemi di funzionalità. Come per molti aspetti dello sviluppo web, la chiave sta nel trovare il giusto equilibrio tra apertura e protezione, tra funzionalità e sicurezza.

In un mondo dove le applicazioni web diventano sempre più complesse e distribuite, CORS continuerà a svolgere un ruolo fondamentale nell'ecosistema di sicurezza del web, proteggendo gli utenti mentre consente le innovazioni che rendono il web una piattaforma così potente e versatile.

Risorse utili