Step-by-step solutions for Cross-Origin Resource Sharing problems — server configs for Express, Next.js, Apache, Nginx
The server's response does not include the CORS header that authorizes your origin. The browser blocks the response.
Access-Control-Allow-Origin header to the response. See server examples below.The browser sent an OPTIONS preflight request (because your request has custom headers or non-simple method). The server responded with non-200 status or missing CORS headers for OPTIONS.
// Express example — before your route handler
app.options('/api/data', cors()); // preflight responderAuthorization, X-), or Content-Type other than application/x-www-form-urlencoded, multipart/form-data, text/plain.Your frontend sets withCredentials: true or credentials: 'include' (cookies, auth headers), but the server did not include Access-Control-Allow-Credentials: true in its response.
Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin cannot be *. You must echo the specific origin:Access-Control-Allow-Origin: https://myapp.comAccess-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. It's invalid and will be rejected by browsers.Your request includes a header that the server hasn't explicitly allowed in its Access-Control-Allow-Headers response header.
Access-Control-Allow-Headers:Access-Control-Allow-Headers: Authorization, X-Custom-Header, Content-TypeAuthorization, X-Requested-With, X-CSRF-Token. Access-Control-Allow-Headers: * is valid in Fetch spec but not universally supported; list explicitly for compatibility.Your request uses a method not listed in the server's Access-Control-Allow-Methods header in the preflight response.
Access-Control-Allow-Methods:Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS, PATCHOPTIONS must be allowed because the browser sends an OPTIONS preflight request before the actual method.You want to allow several specific origins (not *). Echo back the Origin request header if it's in your whitelist.
// Express.js
const allowed = ["https://myapp.com", "https://admin.myapp.com"];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowed.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}));// Next.js — middleware.js
export function middleware(request) {
const response = NextResponse.next();
const origin = request.headers.get('origin');
const allowed = ["https://myapp.com", "https://app.myapp.com"];
if (allowed.includes(origin)) {
response.headers.set('Access-Control-Allow-Origin', origin);
response.headers.set('Access-Control-Allow-Credentials', 'true');
}
return response;
}!origin check allows same-origin requests (mobile apps, Postman) that send no Origin header.# .htaccess in document root
<IfModule mod_headers.c>
Header always set Access-Control-Allow-Origin "https://myapp.com"
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header always set Access-Control-Allow-Headers "Authorization, Content-Type"
Header always set Access-Control-Allow-Credentials "true"
</IfModule># Handle OPTIONS preflight
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]mod_headers enabled (a2enmod headers). For dynamic origin, need mod_setenvif or server-side scripting (PHP/Python).# nginx.conf or site config
location /api/ {
add_header 'Access-Control-Allow-Origin' 'https://myapp.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
# Preflight
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}always ensures headers are set even on error responses. Access-Control-Max-Age (seconds) caches preflight in browser. 1728000 = 20 days.export default {
async fetch(request, env, ctx) {
const response = await fetch(request);
const newHeaders = new Headers(response.headers);
newHeaders.set('Access-Control-Allow-Origin', 'https://myapp.com');
newHeaders.set('Access-Control-Allow-Credentials', 'true');
return new Response(response.body, {
status: response.status,
headers: newHeaders
});
}
};Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: truehttp://localhost:3000, http://127.0.0.1:5173, etc.Access-Control-Allow-Origin: http://localhost:3000
https://myapp.comCORS validation is per-response. A redirect response must itself have the appropriate Access-Control-Allow-Origin header if it's cross-origin, but usually redirects don't include CORS headers.
Access-Control-Allow-Origin (rarely practical).The CORS Errors fix page provides step-by-step solutions for the most common Cross-Origin Resource Sharing issues. It covers browser-blocked errors (missing headers, preflight failures, credentials mismatches) and server-side configurations for Express, Next.js, Apache, Nginx, and Cloudflare Workers.
GET/POST with "simple" headers (Accept, Content-Type限于 text/plain, application/x-www-form-urlencoded, multipart/form-data). Browser sends request with Origin header. Server responds with Access-Control-Allow-Origin.
Any request with custom headers or methods other than GET/POST/PUT/DELETE with simple content-type triggers OPTIONS preflight. Server must respond to OPTIONS with Access-Control-Allow-Methods and Access-Control-Allow-Headers, then handle actual request with Access-Control-Allow-Origin.
When withCredentials: true (cookies, HTTP auth), server must send Access-Control-Allow-Credentials: true AND specific origin (not *). Also Access-Control-Allow-Credentials cannot be used with Access-Control-Allow-Origin: *.
If you can't modify the API server, create a proxy on your own domain. Same-origin requests have no CORS restrictions. Vercel's middleware, Cloudflare Workers, or a simple Express route can proxy and add headers.
No. CORS is a browser-enforced read-access control, not a write-protection. CSRF attacks can still occur via non-CORS-enforced mechanisms (form posts, image tags). Use CSRF tokens, SameSite cookies, or double-submit patterns for CSRF protection. CORS and CSRF solve different problems.
Technically, Access-Control-Allow-Origin: * is valid for public APIs that don't use credentials. If your API requires cookies or Authorization headers, you must list specific origins. Wildcard + credentials is illegal per spec and browsers reject it.
Origin is scheme://host:port. http://localhost:3000 and http://localhost:5173 have different ports → different origins. Even http://127.0.0.1:3000 differs from http://localhost:3000 (hostname differs). For local development, add both to your allowed origins list.
Yes, completely free with no sign-up required.