If you've ever seen Access to fetch at '...' has been blocked by CORS policy in your browser console, you know the frustration. CORS errors are one of the most common issues web developers encounter, yet the underlying mechanism is often misunderstood. This guide explains CORS from first principles so you can fix these errors — and understand why they exist.
What Is the Same-Origin Policy?
Browsers enforce a security rule called the Same-Origin Policy (SOP). Two URLs have the same origin if they share the same protocol, hostname, and port.
https://example.com/page1 → https://example.com/page2 ✅ Same origin https://example.com → http://example.com ❌ Different protocol https://example.com → https://api.example.com ❌ Different hostname https://example.com → https://example.com:8080 ❌ Different port
The SOP prevents a malicious site from reading data from another site. Without it, any website could make requests to your bank's API using your cookies.
What Is CORS?
CORS (Cross-Origin Resource Sharing) is a mechanism that relaxes the Same-Origin Policy. It lets a server explicitly declare which other origins are allowed to access its resources. The server communicates this through HTTP response headers.
How CORS Works
Simple Requests
For simple GET/POST requests with standard headers, the browser sends the request directly and checks the response headers:
// Browser sends:
GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://myapp.com
// Server responds:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://myapp.com
Content-Type: application/json
{"data": "..."}
Preflight Requests (OPTIONS)
For "non-simple" requests (custom headers, PUT/DELETE methods, JSON content type), the browser first sends an OPTIONS preflight request to ask the server for permission.
// 1. Browser sends preflight: OPTIONS /api/users HTTP/1.1 Host: api.example.com Origin: https://myapp.com Access-Control-Request-Method: DELETE Access-Control-Request-Headers: Content-Type, Authorization // 2. Server responds with allowed methods/headers: HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://myapp.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Max-Age: 86400 // 3. Browser sends the actual request (only if preflight succeeds)
CORS Response Headers
Access-Control-Allow-Origin: https://myapp.com // Which origins can access Access-Control-Allow-Methods: GET, POST, PUT // Allowed HTTP methods Access-Control-Allow-Headers: Content-Type, Auth // Allowed request headers Access-Control-Allow-Credentials: true // Allow cookies/auth Access-Control-Max-Age: 86400 // Cache preflight (seconds) Access-Control-Expose-Headers: X-Request-Id // Headers readable by JS
Server Configuration
import cors from 'cors';
app.use(cors({
origin: ['https://myapp.com', 'https://staging.myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400
}));
location /api/ {
add_header Access-Control-Allow-Origin "https://myapp.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
if ($request_method = OPTIONS) {
return 204;
}
}
header("Access-Control-Allow-Origin: https://myapp.com");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
Common CORS Mistakes
- Using
Access-Control-Allow-Origin: *with credentials: The browser will block this. You must specify the exact origin whencredentials: true. - CORS is a browser security feature: It does NOT protect your API from server-to-server requests. Always validate authentication on every request.
- Forgetting the
alwaysflag in Nginx: Without it, CORS headers aren't sent on error responses (4xx, 5xx). - Not handling OPTIONS requests: If your server returns 404 or 405 for OPTIONS, all preflight requests fail.
Try Our Free Developer Tools
Encode URLs, decode Base64, and format JSON instantly.