CORS WordPress REST API è il ostacolo numero uno per agenzie che implementano architetture headless, integrazioni JavaScript lato client o applicazioni che parlano con WordPress da domini diversi. Nella nostra esperienza su oltre 50 siti WordPress gestiti con AgencyPilot, abbiamo risolto problemi CORS almeno una volta a settimana — spesso con soluzioni sbagliate che introducevano vulnerabilità di sicurezza.
In questa guida completa vedrai come configurare correttamente i CORS headers per la WordPress REST API, gestire le richieste preflight OPTIONS, evitare le insidie più comuni e mantenere la sicurezza in produzione. Che tu stia costruendo un frontend Next.js, una SPA React o un’integrazione con un servizio esterno, troverai il snippet pronto da usare.
TL;DR — Configurazione CORS in 30 Secondi
Se hai fretta e vuoi solo il codice che funziona, ecco la versione sicura per produzione. Mettilo in un mu-plugin (must-use plugin) o in functions.php del tuo tema:
// CORS sicuro per produzione — allowlist di origini
add_filter('rest_pre_serve_request', function($served, $result, $request, $server) {
$allowed_origins = array(
'https://app.example.com',
'https://www.example.com',
'http://localhost:3000', // solo per dev
);
$origin = get_http_origin();
if ($origin && in_array($origin, $allowed_origins, true)) {
header('Access-Control-Allow-Origin: ' . esc_url_raw($origin));
header('Vary: Origin');
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce');
header('Access-Control-Max-Age: 600');
}
if ('OPTIONS' === $_SERVER['REQUEST_METHOD']) {
status_header(200);
exit;
}
return $served;
}, 10, 4);
Questo snippet usa una allowlist esplicita di origini, supporta credenziali, gestisce il preflight e include Vary: Origin per evitare problemi di caching. Leggi il resto dell’articolo per capire perché ogni riga è necessaria.
Cosa Sono i CORS e Perché WordPress Ne Ha Bisogno
CORS (Cross-Origin Resource Sharing) è un meccanismo di sicurezza del browser che controlla quando una pagina web può fare richieste a un server su un dominio diverso (origine diversa). Quando il tuo frontend su app.example.com chiama la REST API di WordPress su wp.example.com, il browser verifica che il server WordPress invii gli header CORS corretti. Se non li trova, blocca la risposta.
Un’origine è definita dalla combinazione di schema + dominio + porta. Quindi https://example.com e https://www.example.com sono origini diverse. Lo stesso vale per http://localhost:3000 e http://localhost:8080.
Quando CORS si attiva
CORS si applica solo alle richieste cross-origin fatte dal browser (client-side). Le richieste server-to-server — come un componente server Next.js che chiama WordPress — non sono soggette a CORS perché il browser non è coinvolto. Questo è un dettaglio fondamentale che spesso confonde gli sviluppatori:
| Tipo di richiesta | CORS si applica? | Esempio |
|---|---|---|
| Browser → API cross-origin | Sì | fetch() da React a WordPress REST API |
| Server → API cross-origin | No | Next.js getStaticProps() che chiama WordPress |
| Browser → API same-origin | No | JavaScript WordPress che chiama /wp-json/ sullo stesso dominio |
| Server → Server | No | Webhook PHP che chiama un’API esterna |
I 5 CORS Header essenziali
Per configurare correttamente CORS su WordPress, devi comprendere ogni header:
- Access-Control-Allow-Origin: specifica quale origine può accedere alla risorsa. Usa un’origine specifica (es.
https://app.example.com) per produzione. Mai usare*se invii credenziali. - Access-Control-Allow-Methods: i metodi HTTP permessi per richieste cross-origin (GET, POST, PUT, PATCH, DELETE, OPTIONS).
- Access-Control-Allow-Headers: gli header che il client può inviare nella richiesta reale (Authorization, Content-Type, X-WP-Nonce, ecc.).
- Access-Control-Allow-Credentials: se
true, permette l’invio di cookie e credenziali. Richiede un’origine esplicita, non*. - Access-Control-Max-Age: per quanto tempo (in secondi) il browser può cachare la risposta preflight. Riduce le richieste OPTIONS.
Esiste anche Vary: Origin, che non è un CORS header vero e proprio ma è critico: dice a CDN e cache di conservare risposte separate per origini diverse, evitando che un utente riceva gli header CORS di un altro utente.
Configurazione CORS per WordPress: 3 Livelli
Esistono tre livelli dove puoi configurare CORS su WordPress. Ognuno ha pro e contro. Nella nostra agenzia usiamo una combinazione: Nginx per le performance e PHP come fallback per la flessibilità.
Livello 1: PHP (functions.php o mu-plugin)
Il livello più comune e flessibile. Usa il filtro rest_pre_serve_request che si attiva appena prima che la REST API invii la risposta. Questo è il hook corretto per aggiungere CORS header perché viene eseguito su ogni risposta REST:
// mu-plugins/cors-rest-api.php
add_filter('rest_pre_serve_request', function($served, $result, $request, $server) {
// Allowlist di origini — AGGIORNA con i tuoi domini
$allowed_origins = array(
'https://app.tuosito.com',
'https://admin.tuosito.com',
);
$origin = get_http_origin();
if ($origin && in_array($origin, $allowed_origins, true)) {
header('Access-Control-Allow-Origin: ' . esc_url_raw($origin));
header('Vary: Origin');
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
header('Access-Control-Allow-Credentials: true');
// Echo degli header richiesti dal client nel preflight
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
$req_headers = sanitize_text_field(
wp_unslash($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])
);
header('Access-Control-Allow-Headers: ' . $req_headers);
} else {
header('Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce');
}
header('Access-Control-Max-Age: 600');
}
// Gestione preflight OPTIONS
if ('OPTIONS' === $_SERVER['REQUEST_METHOD']) {
status_header(200);
exit;
}
return $served;
}, 10, 4);
Vantaggi PHP: logica condizionale (puoi verificare l’utente, il tipo di endpoint, ecc.), compatibilità con qualsiasi hosting, manutenzione dal codebase WordPress.
Svantaggi PHP: se c’è un layer di cache (Varnish, Redis, CDN), gli header potrebbero non essere inviati sulle risposte cachate. Per questo motivo, per siti in produzione consigliamo sempre anche la configurazione a livello server.
Livello 2: Nginx
Se gestisci i server (come facciamo noi con AgencyPilot per i siti dei clienti), la configurazione a livello Nginx è preferibile per performance. Gli header vengono impostati prima che PHP si avvii, funzionano anche su risposte cachate e sono più efficienti:
# /etc/nginx/sites-available/wp-site.conf
location /wp-json/ {
# Allowlist di origini con regex
set $cors_origin "";
if ($http_origin ~* "^https?://(app\.tuosito\.com|admin\.tuosito\.com|localhost:3000)$") {
set $cors_origin $http_origin;
}
# Gestione preflight OPTIONS
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-WP-Nonce' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Max-Age' 86400 always;
add_header 'Vary' 'Origin' always;
add_header 'Content-Length' 0 always;
return 204;
}
# Header CORS su tutte le risposte REST
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Vary' 'Origin' always;
# Proxy a PHP-FPM
try_files $uri $uri/ /index.php?$args;
}
Nota l’uso di always su add_header: senza always, Nginx non invia gli header sui codici di errore (403, 404, 500), creando problemi CORS apparentemente casuali.
Livello 3: Apache (.htaccess)
Per hosting condivisi dove non puoi modificare Nginx, Apache è l’opzione disponibile. Usa .htaccess nella root di WordPress:
# .htaccess — CORS per REST API
<IfModule mod_headers.c>
# Origini permesse (modifica con i tuoi domini)
SetEnvIf Origin "https?://(app\.tuosito\.com|admin\.tuosito\.com|localhost:3000)$" CORS_ORIGIN=$0
Header set Access-Control-Allow-Origin "%{CORS_ORIGIN}e" env=CORS_ORIGIN
Header set Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" env=CORS_ORIGIN
Header set Access-Control-Allow-Headers "Authorization, Content-Type, X-WP-Nonce" env=CORS_ORIGIN
Header set Access-Control-Allow-Credentials "true" env=CORS_ORIGIN
Header set Access-Control-Max-Age "600" env=CORS_ORIGIN
Header set Vary "Origin" env=CORS_ORIGIN
# Risposta preflight
Header set Access-Control-Allow-Origin "%{CORS_ORIGIN}e" env=CORS_ORIGIN
</IfModule>
# Gestione OPTIONS preflight
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^wp-json/ - [R=204,L]
La configurazione Apache è meno efficiente di Nginx (.htaccess viene valutato ad ogni richiesta) ma funziona su qualsiasi hosting. Se hai accesso root, configura gli header nel virtual host di Apache invece che in .htaccess per migliori performance.
Richieste Preflight OPTIONS: Il Problema Più Comune
Il 90% dei problemi CORS che vediamo nei siti dei nostri clienti riguarda le richieste preflight OPTIONS. Quando il browser vuole fare una richiesta “non semplice” (POST con JSON, qualsiasi richiesta con Authorization header, o metodi diversi da GET/HEAD/POST con Content-Type specifici), invia prima una richiesta OPTIONS per verificare le autorizzazioni.
WordPress non gestisce nativamente le richieste OPTIONS. Senza configurazione aggiuntiva, la richiesta OPTIONS arriva a WordPress, che cerca di eseguirla come una richiesta normale, restituisce 404 o 405, e il browser blocca la richiesta reale.
Sintomi del problema preflight
Nella console del browser vedi errori come:
Access to fetch at 'https://wp.example.com/wp-json/wp/v2/posts'
from origin 'https://app.example.com' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Se guardi nel tab Network, vedi una richiesta OPTIONS che restituisce 404 o 405. Questo significa che WordPress non ha intercettato la richiesta preflight.
Soluzione: intercettare OPTIONS su init
Per gestire il preflight prima che WordPress faccia qualsiasi routing, usa l’hook init con priorità alta:
// Intercetta OPTIONS prima del routing REST API
add_action('init', function() {
if ('OPTIONS' !== $_SERVER['REQUEST_METHOD']) {
return;
}
// Verifica che sia una richiesta alla REST API
$request_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
if (strpos($request_uri, '/wp-json/') === false) {
return;
}
$allowed_origins = array(
'https://app.tuosito.com',
'https://admin.tuosito.com',
);
$origin = get_http_origin();
if ($origin && in_array($origin, $allowed_origins, true)) {
header('Access-Control-Allow-Origin: ' . esc_url_raw($origin));
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce');
header('Access-Control-Max-Age: 86400');
header('Vary: Origin');
}
status_header(204);
exit;
}, 1);
L’uso di priority 1 garantisce che questo hook venga eseguito prima di quasi tutto il resto. Il status_header(204) invia un “No Content” che è la risposta standard per un preflight riuscito.
CORS e Autenticazione WordPress: X-WP-Nonce e Cookie
Se il tuo frontend usa l’autenticazione nonce di WordPress (il metodo standard per JavaScript embeddato in WordPress), il client invia l’header X-WP-Nonce. Il browser includerà questo header nella richiesta preflight Access-Control-Request-Headers, e il server deve rispondere con Access-Control-Allow-Headers che include X-WP-Nonce.
Se invece usi Application Passwords (Basic Auth), il client invia Authorization. Lo stesso vale per JWT: il token viaggia nell’header Authorization: Bearer ....
Combinare nonce e credenziali
Quando usi cookie di sessione WordPress (per utenti autenticati) insieme a nonce, devi impostare Access-Control-Allow-Credentials: true. Questo richiede che Access-Control-Allow-Origin sia un’origine specifica, non *. Ecco un esempio completo per un setup headless con autenticazione mista:
// CORS con supporto nonce + cookie per headless
add_filter('rest_pre_serve_request', function($served) {
$allowed_origins = array(
'https://app.tuosito.com',
);
$origin = get_http_origin();
if (!$origin || !in_array($origin, $allowed_origins, true)) {
return $served;
}
header('Access-Control-Allow-Origin: ' . esc_url_raw($origin));
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
// Riflette gli header richiesti nel preflight
$allowed_headers = array('Authorization', 'Content-Type', 'X-WP-Nonce');
$request_headers = isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])
? sanitize_text_field(wp_unslash($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
: '';
if ($request_headers) {
// Verifica che gli header richiesti siano nella allowlist
$requested = array_map('trim', explode(',', $request_headers));
$safe = array_filter($requested, function($h) use ($allowed_headers) {
return in_array(trim($h), $allowed_headers, true);
});
if (!empty($safe)) {
header('Access-Control-Allow-Headers: ' . implode(', ', $safe));
}
} else {
header('Access-Control-Allow-Headers: ' . implode(', ', $allowed_headers));
}
header('Vary: Origin');
header('Access-Control-Max-Age: 86400');
if ('OPTIONS' === $_SERVER['REQUEST_METHOD']) {
status_header(204);
exit;
}
return $served;
}, 10);
Errori CORS Comuni e Come Risolverli
Nella nostra esperienza gestendo siti WordPress per agenzie, abbiamo visto ogni tipo di errore CORS immaginabile. Ecco i più frequenti e le relative soluzioni.
1. Header CORS duplicati
Se configuri CORS sia in PHP che in Nginx, il browser riceve due Access-Control-Allow-Origin e lo blocca. Soluzione: scegli un solo livello. Noi usiamo Nginx per le performance e rimuoviamo il filtro PHP.
# Nginx — rimuovi header duplicati da PHP
fastcgi_hide_header Access-Control-Allow-Origin;
fastcgi_hide_header Access-Control-Allow-Methods;
fastcgi_hide_header Access-Control-Allow-Headers;
2. Origin non corrisponde esattamente
https://example.com non è https://www.example.com. Se il tuo frontend è su www ma la allowlist ha solo https://example.com, CORS fallirà. Includi sempre entrambe le varianti:
$allowed_origins = array(
'https://example.com',
'https://www.example.com',
'http://localhost:3000',
'http://localhost:5173', // Vite dev server
);
3. Plugin di sicurezza che rimuovono header
Plugin come Wordfence, Solid Security (ex iThemes Security) o Cloudflare WAF possono intercettare e rimuovere gli header CORS. Verifica sempre con curl che gli header arrivino:
# Test CORS con curl
curl -I -X OPTIONS -H "Origin: https://app.tuosito.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: Authorization, Content-Type" https://wp.tuosito.com/wp-json/wp/v2/posts
Se gli header non ci sono, disattiva temporaneamente i plugin di sicurezza e testa di nuovo.
4. Cache che nasconde gli header
Varnish, Redis Cache o Cloudflare possono cachare le risposte REST senza gli header CORS. Soluzioni:
- Aggiungi
Vary: Origin(sempre!) per dire alla cache di separare le risposte per origine - Escludi
/wp-json/dalla page cache di Varnish/Redis - Configura Cloudflare per non cachare le risposte API
5. Access-Control-Allow-Origin: * con credenziali
Questo è l’errore più pericoloso. Se usi * come origine e tenti di inviare credenziali, il browser blocca tutto. * è compatibile solo con richieste senza credenziali. Per qualsiasi setup che usa cookie, nonce o Authorization, devi usare un’origine esplicita.
Configurazione CORS per Headless WordPress con Next.js
Per i progetti headless con Next.js — un pattern che usiamo sempre più spesso per i siti dei clienti — la configurazione CORS ha sfumature specifiche. Next.js ha componenti server e componenti client: solo i componenti client hanno bisogno di CORS.
Strategia: server-to-server per dati protetti
La soluzione più pulita è fare tutte le richieste WordPress in Server Components o API routes di Next.js. Queste girano sul server, non nel browser, e bypassano CORS completamente:
// app/page.tsx — Server Component, nessun CORS necessario
async function getPosts() {
const res = await fetch('https://wp.tuosito.com/wp-json/wp/v2/posts', {
headers: {
'Authorization': 'Basic ' + Buffer.from(
process.env.WP_USER + ':' + process.env.WP_APP_PASSWORD
).toString('base64')
},
next: { revalidate: 60 } // ISR: refresh ogni 60 secondi
});
if (!res.ok) throw new Error('Failed to fetch posts');
return res.json();
}
export default async function HomePage() {
const posts = await getPosts();
return (
<main>
{posts.map(post => (
<article key={post.id}>
<h1>{post.title.rendered}</h1>
<div dangerouslySetInnerHTML={{__html: post.excerpt.rendered}} />
</article>
))}
</main>
);
}
Quando servono CORS in Next.js
Hai bisogno di CORS solo quando fai fetch dal client. Esempi: form di ricerca, infinite scroll, interazioni dinamiche. In questi casi, configura CORS su WordPress e chiama l’API direttamente dal browser:
// components/SearchForm.tsx — Client Component
'use client';
import { useState } from 'react';
export function SearchForm() {
const [results, setResults] = useState([]);
async function handleSearch(query: string) {
const res = await fetch(
`https://wp.tuosito.com/wp-json/wp/v2/posts?search=${encodeURIComponent(query)}`,
{
headers: {
'X-WP-Nonce': window.wpApiSettings?.nonce,
},
credentials: 'include', // necessario per cookie
}
);
setResults(await res.json());
}
// ... rendering
}
In questo caso, WordPress deve avere CORS configurato con l’origine del frontend Next.js nella allowlist.
Sicurezza CORS: Best Practice per Agenzie
Una configurazione CORS sbagliata può esporre la tua REST API ad abusi. Ecco le best practice che applichiamo su tutti i siti dei clienti di AgencyPilot.
Usa sempre una allowlist di origini
Mai usare Access-Control-Allow-Origin: * in produzione. Anche se non invii credenziali, un’origine wildcard permette a qualsiasi sito di fare richieste alla tua API. Definisci esplicitamente le origini permesse:
// Configurazione per-multi-sito
$site_origins = array(
'main' => array(
'https://tuosito.com',
'https://www.tuosito.com',
),
'app' => array(
'https://app.tuosito.com',
),
'staging' => array(
'https://staging.tuosito.com',
),
);
// Combina tutte le origini permesse
$allowed_origins = array_merge(
$site_origins['main'],
$site_origins['app'],
$site_origins['staging']
);
// In produzione, rimuovi localhost
if (defined('WP_ENV') && WP_ENV === 'production') {
$allowed_origins = array_filter($allowed_origins, function($origin) {
return strpos($origin, 'localhost') === false;
});
}
Disabilita enumerazione utenti sulla REST API
Indipendentemente dai CORS, l’endpoint /wp-json/wp/v2/users espone nomi utente senza autenticazione. Gli attaccanti lo usano per enumerare account validi prima di attacchi brute-force:
// Rimuovi endpoint users per utenti non autenticati
add_filter('rest_endpoints', function($endpoints) {
if (!is_user_logged_in()) {
if (isset($endpoints['/wp/v2/users'])) {
unset($endpoints['/wp/v2/users']);
}
if (isset($endpoints['/wp/v2/users/(?P[\d]+)'])) {
unset($endpoints['/wp/v2/users/(?P[\d]+)']);
}
}
return $endpoints;
});
Implementa rate limiting sulla REST API
CORS controlla chi può chiamare la tua API dal browser, ma non limita il volume di richieste. Aggiungi rate limiting a livello Nginx per prevenire abusi:
# Rate limiting REST API in Nginx
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /wp-json/ {
limit_req zone=api burst=20 nodelay;
# ... resto della configurazione CORS
try_files $uri $uri/ /index.php?$args;
}
Logga le richieste CORS fallite
Per diagnosticare problemi in produzione, logga le richieste dove l’origine non è nella allowlist:
// Log richieste CORS bloccate (per audit sicurezza)
add_filter('rest_pre_serve_request', function($served) {
$origin = get_http_origin();
$allowed_origins = array('https://app.tuosito.com');
if ($origin && !in_array($origin, $allowed_origins, true)) {
error_log(sprintf(
'CORS blocked: origin=%s uri=%s ip=%s',
$origin,
$_SERVER['REQUEST_URI'] ?? '',
$_SERVER['REMOTE_ADDR'] ?? ''
));
}
return $served;
}, 5); // Priority bassa, esegue prima del filtro CORS principale
Testare la Configurazione CORS
Dopo aver configurato CORS, testa che funzioni correttamente. Ecco i metodi che usiamo noi per verificare ogni setup.
Test con curl
Il modo più veloce per verificare gli header CORS è curl. Simula una richiesta preflight:
# Test preflight OPTIONS
curl -v -X OPTIONS -H "Origin: https://app.tuosito.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: Authorization, Content-Type" https://wp.tuosito.com/wp-json/wp/v2/posts
# Verifica gli header di risposta:
# Access-Control-Allow-Origin: https://app.tuosito.com
# Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
# Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce
# Access-Control-Allow-Credentials: true
# Vary: Origin
# HTTP/1.1 200 OK (o 204)
# Test richiesta GET cross-origin
curl -v -H "Origin: https://app.tuosito.com" https://wp.tuosito.com/wp-json/wp/v2/posts?per_page=1
# Verifica: Access-Control-Allow-Origin presente nella risposta
Test dal browser
Apri DevTools → Console e fai una fetch di test:
// Test CORS dal browser
fetch('https://wp.tuosito.com/wp-json/wp/v2/posts?per_page=1')
.then(r => {
console.log('Status:', r.status);
console.log('CORS Origin:', r.headers.get('Access-Control-Allow-Origin'));
return r.json();
})
.then(data => console.log('Posts:', data.length))
.catch(err => console.error('CORS Error:', err));
Test origine non autorizzata
Verifica che un’origine non nella allowlist venga bloccata:
# Origin non autorizzata — non deve ricevere header CORS
curl -I -H "Origin: https://evil.example.com" https://wp.tuosito.com/wp-json/wp/v2/posts
# Non deve esserci Access-Control-Allow-Origin nella risposta
CORS e CDN: Configurazione Cloudflare
Molti nostri clienti usano Cloudflare davanti a WordPress. La CDN introduce un layer aggiuntivo che può interferire con CORS. Ecco la configurazione corretta.
Regola Cloudflare per CORS
In Cloudflare → Rules → Transform Rules, crea una regola che preserva l’header Vary: Origin:
- Vai su Cloudflare Dashboard → Rules → Configuration Rules
- Crea una regola per i path che iniziano con
/wp-json/ - Imposta “Browser Cache TTL” su “Respect Existing Headers”
- Attiva “Origin Cache Control” per rispettare
Vary: Origin
Page Rules per bypassare la cache sull’API
Per endpoint dinamici (POST, PUT, DELETE), la cache di Cloudflare non deve intercettare. Crea una Page Rule:
URL: *tuosito.com/wp-json/*
Cache Level: Bypass
Questo assicura che le risposte REST API non vengano cachate da Cloudflare, mantenendo gli header CORS dinamici per ogni origine.
FAQ — CORS WordPress REST API
Cos’è CORS e perché blocca le richieste alla REST API di WordPress?
CORS (Cross-Origin Resource Sharing) è un meccanismo di sicurezza del browser che impedisce a una pagina web di fare richieste a un server su un dominio diverso senza autorizzazione esplicita. WordPress non invia header CORS per default sulla REST API, quindi il browser blocca le richieste cross-origin. La soluzione è aggiungere gli header Access-Control-Allow-Origin e correlati tramite PHP (rest_pre_serve_request), Nginx o Apache.
Come risolvere l’errore “No ‘Access-Control-Allow-Origin’ header is present”?
Questo errore significa che WordPress non sta inviando gli header CORS. Le cause più comuni sono: 1) configurazione CORS mancante in functions.php o mu-plugin, 2) plugin di sicurezza che rimuovono gli header, 3) cache (Varnish/Cloudflare) che nasconde gli header, 4) header CORS duplicati tra PHP e Nginx. Verifica con curl -I quali header arrivano effettivamente e correggi il livello problematico.
Access-Control-Allow-Origin: * è sicuro per la REST API di WordPress?
No, * non è sicuro se la tua API usa autenticazione (cookie, nonce, Application Passwords). Con * il browser non può inviare credenziali (Access-Control-Allow-Credentials: true non funziona con wildcard). Per API pubbliche senza autenticazione, * è accettabile ma comunque sconsigliato perché permette a qualsiasi sito di consumare la tua API. Usa sempre una allowlist di origini specifiche.
Le richieste server-to-server hanno problemi CORS?
No. CORS è un meccanismo del browser. Le richieste fatte dal server (come Next.js Server Components, webhook PHP, script Python) non sono soggette a CORS perché il browser non è coinvolto. Se hai problemi CORS, spostare la logica di fetch lato server elimina il problema. È la soluzione più pulita per architetture headless.
Come gestire CORS con Application Passwords su WordPress?
Le Application Passwords usano HTTP Basic Auth, quindi il client invia l’header Authorization. Il browser includerà Authorization nella richiesta preflight Access-Control-Request-Headers. Il server deve rispondere con Access-Control-Allow-Headers: Authorization nel preflight. Inoltre, non serve Access-Control-Allow-Credentials: true per Basic Auth (le credenziali sono nell’header, non nei cookie), ma devi comunque usare un’origine esplicita, non *.
WordPress disabilita la REST API per utenti non autenticati: è compatibile con CORS?
Sì. Puoi usare il filtro rest_authentication_errors per richiedere autenticazione su tutta la REST API e contemporaneamente configurare CORS per permettere le richieste autenticate dai domini autorizzati. Le due configurazioni sono indipendenti: CORS controlla chi può chiamare dal browser, l’autenticazione controlla chi può accedere. Però devi assicurarti che gli header CORS vengano inviati anche sulle risposte 401 (Unauthorized), altrimenti il browser non può leggere il messaggio di errore.