The error “Access to fetch at ‘…’ from origin ‘…’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present” is one of the most common web development errors. It’s enforced by the browser as a security mechanism. Here’s every cause and fix.
📋 Table of Contents
- Why CORS Exists (Required Understanding)
- Fix 1: Express.js — Using cors Package
- Fix 2: Manual CORS Headers in Express
- Fix 3: Nginx Reverse Proxy
- Fix 4: Frontend — Credentials Mode
- Understanding Preflight Requests
- Development Proxy (Avoid CORS During Development)
- Common Mistakes
- Frequently Asked Questions
- Conclusion
🔑 Key Takeaway
The error “Access to fetch at ‘…’ from origin ‘…’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present” is one of the most common web development errors. It’s enforced by the browser as a security mechanism.
Why CORS Exists (Required Understanding)
CORS (Cross-Origin Resource Sharing) prevents malicious websites from making requests to your bank’s API using your logged-in cookies. Without CORS, visiting evil.com could silently make API calls to yourbank.com with your session cookies. The browser enforces CORS — servers declare which origins are allowed.
CORS is a browser security feature — it only applies to browser-initiated requests. curl, Postman, and server-to-server requests are not affected by CORS.
Fix 1: Express.js — Using cors Package
npm install cors
const express = require('express');
const cors = require('cors');
const app = express();
// ✅ Allow specific origin (recommended for production)
app.use(cors({
origin: 'https://yourfrontend.com',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // required if frontend sends cookies/auth headers
}));
// ✅ Allow multiple origins
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error(`Origin ${origin} not allowed by CORS`));
}
},
credentials: true,
}));
// ❌ Allow all origins (never in production with credentials)
app.use(cors({ origin: '*' }));
// Note: wildcard '*' cannot be used with credentials: true
Fix 2: Manual CORS Headers in Express
// Without the cors package
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://yourfrontend.com');
res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization');
res.header('Access-Control-Allow-Credentials', 'true');
// Handle preflight requests
if (req.method === 'OPTIONS') {
return res.status(204).end();
}
next();
});
Fix 3: Nginx Reverse Proxy
location /api/ {
proxy_pass http://localhost:3000/;
# CORS headers
add_header 'Access-Control-Allow-Origin' 'https://yourfrontend.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
# Handle preflight
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://yourfrontend.com';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
}
Fix 4: Frontend — Credentials Mode
// ❌ Fetch without credentials — cookies not sent
const res = await fetch('https://api.example.com/user');
// ✅ Include credentials (cookies, authorization headers)
const res = await fetch('https://api.example.com/user', {
credentials: 'include', // send cookies cross-origin
});
// For axios:
axios.defaults.withCredentials = true;
// Or per-request:
axios.get('/api/user', { withCredentials: true });
Important: When credentials: 'include', the server must set Access-Control-Allow-Origin to the specific origin (not *) AND Access-Control-Allow-Credentials: true.
Understanding Preflight Requests
For “non-simple” requests (PUT, DELETE, custom headers, application/json body), the browser sends an OPTIONS preflight request first to check if the actual request is allowed. If the preflight fails, the actual request never executes.
// Browser sends OPTIONS before POST with JSON body:
OPTIONS /api/users HTTP/1.1
Origin: https://frontend.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
// Server must respond:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://frontend.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400 // cache preflight for 24 hours
Development Proxy (Avoid CORS During Development)
// vite.config.ts — proxy /api to backend during dev
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^/api/, ''),
}
}
}
};
// package.json (Create React App) — proxy all /api requests
{
"proxy": "http://localhost:3000"
}
The proxy makes requests from the same origin as the frontend (from the dev server), bypassing CORS entirely during development — without compromising production security.
Common Mistakes
// ❌ Mistake 1: CORS headers on wrong route
app.get('/api/data', (req, res) => {
res.header('Access-Control-Allow-Origin', '*');
res.json(data);
});
// OPTIONS preflight hits a different handler that doesn't have CORS headers
// ❌ Mistake 2: Setting headers after response sent
app.use(cors());
app.get('/data', authenticate, (req, res) => {
res.header('Access-Control-Allow-Origin', '*'); // Too late if cors() runs first
});
// ❌ Mistake 3: Multiple conflicting CORS headers
// Browser rejects when two Access-Control-Allow-Origin headers present
// Don't set CORS in both nginx AND Express — pick one place
Frequently Asked Questions
Q: Can I disable CORS in the browser for development?
A: Yes: open -a "Google Chrome" --args --disable-web-security --user-data-dir="/tmp/chrome-dev". ONLY for local dev — never browse real sites with this flag. Using a proxy is safer and doesn’t require disabling security.
Q: Why does it work in Postman but not the browser?
A: CORS is only enforced by browsers. Postman sends the request directly without a browser CORS check. This confirms your server is working — you just need to add CORS headers.
Q: Should I add CORS headers in the application or the reverse proxy?
A: One place only. If using nginx, add headers in nginx and don’t add them in Express (prevents duplicate headers). If deploying to a service without nginx control, add in Express.
Q: Why does my API work from the same origin but not cross-origin?
A: That’s exactly what CORS prevents — cross-origin requests require explicit server permission. Same-origin requests (frontend and API on same domain/port) don’t need CORS headers.
Q: Can I use a CORS proxy service?
A: For development prototyping, services like cors-anywhere work. Never for production — you’re sending all your API requests through a third-party service. Fix CORS properly on your server.
Conclusion
Fix CORS by adding proper Access-Control-Allow-Origin headers on your server — not by disabling CORS or using browser plugins. For Express.js, the cors npm package handles all cases cleanly. For Nginx, add headers at the proxy level. For development, use Vite’s proxy config to avoid CORS entirely. Always specify exact origins rather than wildcard * in production, especially when using credentials.
📚 You might also like
🔗 Share this article



✍️ Leave a Comment