CORS: Capirlo e Risolverlo Una Volta per Tutte

THEJORD Team1 min di lettura
corssicurezzaapiweb

CORS: capirlo e risolverlo una volta per tutte. Guida completa agli errori Cross-Origin, preflight request e come configurare il server correttamente.

CORS: Capirlo e Risolverlo Una Volta per Tutte

Cos'è CORS

CORS (Cross-Origin Resource Sharing) è un meccanismo di sicurezza implementato dai browser che controlla come le pagine web possono richiedere risorse da domini diversi da quello che ha servito la pagina. È uno degli errori più comuni e frustranti per gli sviluppatori web, ma capirlo a fondo lo rende semplice da risolvere.

Same-Origin Policy

Definizione di Origin

Due URL hanno la stessa origin solo se hanno identici protocollo, host e porta:

// Stessa origin
https://example.com/page1
https://example.com/page2/subpage

// Origine diversa (cross-origin)
https://example.com    vs  http://example.com     (protocollo)
https://example.com    vs  https://api.example.com (host)
https://example.com    vs  https://example.com:8080 (porta)
https://example.com    vs  https://other.com      (dominio)

Perché Esiste

La Same-Origin Policy protegge gli utenti da attacchi malevoli:

  • Session hijacking: Un sito malevolo non può leggere cookie di altri siti
  • Data theft: JavaScript di un sito non può accedere a dati di altri
  • CSRF: Limita le richieste cross-origin non autorizzate

L'Errore CORS

Messaggio Tipico

Access to fetch at 'https://api.example.com/data'
from origin 'https://myapp.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

Cosa Significa

Il browser ha bloccato la risposta perché il server non ha incluso gli header CORS necessari. La richiesta è stata effettivamente inviata e il server ha risposto, ma il browser non permette al JavaScript di accedere alla risposta.

Come Funziona CORS

Richieste Simple

Alcune richieste sono considerate "simple" e non richiedono preflight:

// Condizioni per richiesta simple:
// 1. Metodo: GET, HEAD, POST
// 2. Headers: solo Accept, Accept-Language, Content-Language, Content-Type
// 3. Content-Type: text/plain, multipart/form-data, application/x-www-form-urlencoded

// Esempio simple request
fetch('https://api.example.com/data')
  .then(response => response.json());

// Il browser aggiunge automaticamente:
// Origin: https://myapp.com

// Il server deve rispondere con:
// Access-Control-Allow-Origin: https://myapp.com

Preflight Request

Richieste "non simple" richiedono una richiesta OPTIONS preliminare:

// Questa richiesta triggera un preflight
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',  // Non simple!
    'Authorization': 'Bearer token'       // Custom header!
  },
  body: JSON.stringify({ data: 'value' })
});

// 1. Browser invia OPTIONS (preflight)
OPTIONS /data HTTP/1.1
Host: api.example.com
Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

// 2. Server risponde con permessi
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

// 3. Browser invia richiesta reale
POST /data HTTP/1.1
...

// 4. Server risponde con dati + CORS headers

Headers CORS

Response Headers (Server)

# Headers che il server deve inviare

Access-Control-Allow-Origin: https://myapp.com
# Oppure per tutti (non usare con credentials!):
Access-Control-Allow-Origin: *

Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
# Metodi permessi

Access-Control-Allow-Headers: Content-Type, Authorization, X-Custom-Header
# Headers custom permessi

Access-Control-Allow-Credentials: true
# Permetti cookie/auth (richiede origin specifica, non *)

Access-Control-Expose-Headers: X-Custom-Response-Header
# Headers che JS può leggere dalla risposta

Access-Control-Max-Age: 86400
# Cache preflight per 24 ore

Request Headers (Browser)

# Headers che il browser aggiunge automaticamente

Origin: https://myapp.com
# Origin della richiesta (sempre presente)

Access-Control-Request-Method: POST
# Metodo che verrà usato (solo in preflight)

Access-Control-Request-Headers: Content-Type, Authorization
# Headers custom che verranno usati (solo in preflight)

Configurazione Server

Node.js/Express

// Con middleware cors
const cors = require('cors');

// Configurazione base
app.use(cors());

// Configurazione specifica
app.use(cors({
  origin: 'https://myapp.com',
  // Oppure funzione per logica dinamica:
  // origin: (origin, callback) => { ... }
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400
}));

// Configurazione manuale senza middleware
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://myapp.com');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.header('Access-Control-Allow-Credentials', 'true');

  if (req.method === 'OPTIONS') {
    return res.sendStatus(204);
  }
  next();
});

Next.js API Routes

// pages/api/data.js
export default function handler(req, res) {
  // Set CORS headers
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

  if (req.method === 'OPTIONS') {
    return res.status(200).end();
  }

  // Handle request
  res.json({ data: 'value' });
}

// next.config.js per headers globali
module.exports = {
  async headers() {
    return [{
      source: '/api/:path*',
      headers: [
        { key: 'Access-Control-Allow-Origin', value: '*' }
      ]
    }];
  }
};

Nginx

# nginx.conf
location /api/ {
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '$http_origin';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
        add_header 'Access-Control-Max-Age' 86400;
        return 204;
    }

    add_header 'Access-Control-Allow-Origin' '$http_origin' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;

    proxy_pass http://backend;
}

Credentials e Cookie

// Client: include credentials
fetch('https://api.example.com/data', {
  credentials: 'include'  // Invia cookie
});

// Server: deve rispondere con
Access-Control-Allow-Origin: https://myapp.com  // NON può essere *
Access-Control-Allow-Credentials: true

// IMPORTANTE: con credentials:
// - Origin non può essere *
// - Devi specificare l'origin esatta
// - Il server deve verificare l'origin

Soluzioni Comuni

Proxy Server

// Invece di chiamare API direttamente, usa proxy

// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
};

// Ora: fetch('/api/data') → proxied to api.example.com/data
// Nessun problema CORS (same origin)

Backend for Frontend (BFF)

// Crea un endpoint nel tuo backend che chiama l'API esterna
// /api/external-data

app.get('/api/external-data', async (req, res) => {
  const response = await fetch('https://external-api.com/data', {
    headers: { 'API-Key': process.env.API_KEY }
  });
  const data = await response.json();
  res.json(data);
});

// Il frontend chiama il tuo backend (same origin)
// Il backend chiama l'API esterna (server-to-server, no CORS)

Debugging CORS

// 1. Apri DevTools → Network
// 2. Cerca la richiesta fallita
// 3. Verifica:
//    - Origin header nella request
//    - Access-Control-* headers nella response
//    - Se c'è stata una preflight OPTIONS

// 4. Controlla la console per errori specifici

// Errori comuni:
"No 'Access-Control-Allow-Origin' header"
→ Server non invia header CORS

"The value of the 'Access-Control-Allow-Origin' header...
must not be the wildcard '*' when credentials mode is 'include'"
→ Usa origin specifica invece di *

"Method PUT is not allowed"
→ Aggiungi PUT a Access-Control-Allow-Methods

Strumenti Correlati

Per debug API e web:

Conclusione

CORS non è il nemico—è una protezione importante. Per risolverlo:

  • Configura il server: Aggiungi gli header CORS corretti
  • Usa proxy in sviluppo: Evita CORS durante lo sviluppo
  • BFF pattern: Per API esterne senza controllo
  • Mai disabilitare: Non usare estensioni che disabilitano CORS

Per altri strumenti utili, esplora i nostri tool online gratuiti. Per approfondimenti, consulta MDN CORS Documentation.