Fix CORS Errors

Step-by-step solutions for Cross-Origin Resource Sharing problems — server configs for Express, Next.js, Apache, Nginx

Browser Blocked — Client Side
Client No 'Access-Control-Allow-Origin' header
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.

The server's response does not include the CORS header that authorizes your origin. The browser blocks the response.

If you control the server: Add Access-Control-Allow-Origin header to the response. See server examples below.

If you DON'T control the server: Options:
  1. Use a backend proxy (your server calls the API, adds CORS headers, your frontend calls your server)
  2. Ask the API provider to enable CORS for your origin
  3. For development only: disable browser CORS checks (not production-safe)
Client Response to preflight request doesn't pass access control
Access to XMLHttpRequest at 'https://api.example.com' from origin 'https://myapp.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

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.

  1. 1Ensure your server handles OPTIONS requests for that endpoint:
// Express example — before your route handler
app.options('/api/data', cors()); // preflight responder
  1. 2OR configure CORS middleware to handle preflight automatically (see below).
💡 Preflight happens for: methods other than GET/HEAD/POST, or custom headers (Authorization, X-), or Content-Type other than application/x-www-form-urlencoded, multipart/form-data, text/plain.
Client Credentials flag is 'true', but 'Access-Control-Allow-Credentials' is ''
XMLHttpRequest cannot load 'https://api.example.com'. Credentials flag is 'true', but 'Access-Control-Allow-Credentials' header is ''. Must be 'true' to allow credentials.

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.

  1. 1On the server, add this header:
Access-Control-Allow-Credentials: true
  1. 2Important: When credentials are true, Access-Control-Allow-Origin cannot be *. You must echo the specific origin:
Access-Control-Allow-Origin: https://myapp.com
⚠️ Never set Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. It's invalid and will be rejected by browsers.
Client Request header field X- is not allowed
Access to fetch at 'https://api.example.com' from origin 'https://myapp.com' has been blocked by CORS policy: Request header field x-custom-header is not allowed by Access-Control-Allow-Headers.

Your request includes a header that the server hasn't explicitly allowed in its Access-Control-Allow-Headers response header.

  1. 1On server, add the required header name to Access-Control-Allow-Headers:
Access-Control-Allow-Headers: Authorization, X-Custom-Header, Content-Type
💡 Common headers that trigger preflight: Authorization, X-Requested-With, X-CSRF-Token. Access-Control-Allow-Headers: * is valid in Fetch spec but not universally supported; list explicitly for compatibility.
Client Method PUT/PATCH/DELETE is not allowed
Access to XMLHttpRequest at '...' from origin '...' has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods.

Your request uses a method not listed in the server's Access-Control-Allow-Methods header in the preflight response.

  1. 1On server, include the method in Access-Control-Allow-Methods:
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS, PATCH
ℹ️ OPTIONS must be allowed because the browser sends an OPTIONS preflight request before the actual method.
Server-Side Fixes
Server Dynamic origin — allow multiple domains

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.
Server Apache .htaccess CORS configuration
# .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]
💡 Requires mod_headers enabled (a2enmod headers). For dynamic origin, need mod_setenvif or server-side scripting (PHP/Python).
Server Nginx CORS configuration
# 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.
Server Cloudflare Worker CORS
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
});
}
};
💡 Cloudflare Workers modify responses mid-flight. Useful for adding CORS to third-party APIs you don't control. Deploy as a route in your Worker.
Common Pitfalls

The Wildcard × Credentials Trap

❌ Invalid:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

Forbidden — browsers reject this combination.
✅ Valid:
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true

Specific origin required when credentials = true.

Localhost Origins

Development:
Allow http://localhost:3000, http://127.0.0.1:5173, etc.
Access-Control-Allow-Origin: http://localhost:3000
Production:
Switch to your real domain: https://myapp.com
Use env variables to differ dev/prod config.
Pitfall CORS headers not preserved on redirects
Request to endpoint returns 301/302 redirect. Browser follows redirect but CORS headers from the first response are not applied to the final response.

CORS 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.

  1. 1Update the client to call the final URL directly (avoid redirect).
  1. 2Or ensure the redirect response includes Access-Control-Allow-Origin (rarely practical).
💡 Best practice: don't rely on redirects for cross-origin API endpoints. Use canonical URLs.

About This Tool

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.

CORS Quick Reference

🔐 Simple Request

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.

⚠️ Preflighted Request

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.

🍪 Credentials

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: *.

🚀 Proxies

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.

Frequently Asked Questions

Does CORS protect against CSRF?

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.

Can I allow all origins?

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.

Why does localhost count as a different origin?

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.

Is this tool free?

Yes, completely free with no sign-up required.

Related Tools