What Is CORS?
CORS errors are a browser thing — curl and Postman never complain, which is why they confuse people. The browser is blocking JavaScript from reading the response, not the server from receiving the request. Your request already reached the server; the browser just won't let your code see the result without the right headers saying it's allowed to.
An "origin" is defined as the combination of three things: the protocol (http or https), the domain (example.com), and the port (80, 443, 3000, etc.). Two URLs have the same origin only if all three match exactly.
Same origin as https://app.example.com: https://app.example.com/page ✓ Same https://app.example.com/api/data ✓ Same Different origin (CORS applies): http://app.example.com ✗ Different protocol https://api.example.com ✗ Different subdomain https://app.example.com:8080 ✗ Different port https://other.com ✗ Different domain
Why Does CORS Exist?
CORS exists because of the Same-Origin Policy (SOP), a fundamental security principle that browsers have enforced for decades. Without it, any malicious website you visit could use your browser to make authenticated requests to your bank, your email, or any other service you're logged into — and read the responses.
Consider this scenario: you're logged into your bank at bank.com. You then visit evil.com, which contains JavaScript that tries to fetch bank.com/account/balance. Without the Same-Origin Policy, that JavaScript could read your account balance and send it to the attacker's server. CORS is the mechanism that prevents this by requiring the server (bank.com) to explicitly declare which external origins are allowed to read its responses.
Simple vs. Preflighted Requests
Not all cross-origin requests are treated equally. CORS distinguishes between two types:
Simple Requests
A request is "simple" if it uses GET, POST, or HEAD with only standard headers (Accept, Content-Type with value of text/plain, multipart/form-data, or application/x-www-form-urlencoded). These requests are sent directly, and the browser checks the response headers to decide if JavaScript can access the result.
Preflighted Requests
Any request that doesn't qualify as simple triggers a "preflight" — an automatic OPTIONS request the browser sends before the actual request. The server must respond to this preflight correctly before the browser will send the real request. Preflights are triggered by:
- HTTP methods other than GET, POST, or HEAD (e.g., PUT, DELETE, PATCH)
- Custom headers (e.g., Authorization, X-Custom-Header)
- Content-Type of application/json
This is why your API calls with Authorization headers or JSON bodies trigger CORS errors — they all require preflighting.
How to Fix CORS — Server Configuration
The fix for CORS always happens on the server side. The server must respond with the correct headers to tell the browser which origins, methods, and headers are permitted.
Express.js (Node.js)
const express = require('express'); const cors = require('cors'); const app = express(); // Allow specific origin app.use(cors({ origin: 'https://app.example.com', methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'] }));
Nginx Configuration
location /api/ {
add_header 'Access-Control-Allow-Origin' 'https://app.example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Length' 0;
return 204;
}
}Python (FastAPI)
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)Setting Access-Control-Allow-Origin: * disables CORS protections for your API. If your API uses cookies or Authorization headers, you cannot use wildcard — you must specify exact origins. Using * with credentials enabled will cause browsers to block the request anyway.
Cookies and Credentials
By default, cross-origin requests don't include cookies or HTTP authentication. To send credentials with cross-origin requests, you need both the client and server to opt in explicitly.
// Client-side: include credentials fetch('https://api.example.com/data', { credentials: 'include' // Sends cookies }); // Server must respond with both: Access-Control-Allow-Origin: https://app.example.com // Must be specific, not * Access-Control-Allow-Credentials: true
Common Mistakes and Misconceptions
- CORS is not authentication: CORS headers don't protect your API from unauthorized access — they only control browser behavior. Server-side authentication is separate and still required.
- curl and Postman ignore CORS: These tools don't enforce the Same-Origin Policy because they're not browsers. A working curl request doesn't mean CORS is configured correctly.
- The error is about the response, not the request: The request reaches your server. The browser just won't let JavaScript read the response unless the correct headers are present.
- Don't fix CORS in the frontend: Adding browser extensions or proxy configs only fixes it for you locally. Fix it on the server for all users.
Development Workarounds
During development, it's common to run your frontend on localhost:3000 and your API on localhost:8000. You have a few options:
- Configure your dev server proxy: Vite, webpack, and Create React App all support proxying API requests through the dev server, which avoids CORS entirely.
- Add localhost to your allowed origins: Conditionally allow localhost in development environments through environment variables.
- Run everything on the same port: Serve both frontend and backend from the same origin using a reverse proxy like Nginx even in development.
Conclusion
CORS is often feared but it's one of the most important security mechanisms on the web. It protects users from malicious sites reading sensitive data from services they're authenticated with. The key insight is that CORS is always configured server-side — if you control the server, you control the CORS policy. Understand what your application needs, configure the minimum necessary permissions, and avoid the temptation to simply allow all origins just to silence the error.