You make a request from your frontend to an API and the browser blocks it with: Access to fetch at '...' from origin '...' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. This is one of the most common and misunderstood errors in web development. Here is what actually causes it and how to fix it correctly.
๐ Table of Contents
What CORS Actually Is
CORS (Cross-Origin Resource Sharing) is a browser security mechanism. When your JavaScript on https://app.example.com makes a request to https://api.other.com, the browser considers it a cross-origin request and requires the server to explicitly permit it via response headers. If the server does not send an Access-Control-Allow-Origin header that includes your origin, the browser blocks the response โ even if the server processed the request successfully.
Key insight: CORS is enforced by the browser, and the fix lives on the server, not in your frontend code. You cannot fix a CORS error by changing your fetch call alone.
Fix 1: Configure the Server to Send the Header
The correct fix is to make the API respond with the right CORS headers. In Express, use the cors middleware:
const cors = require('cors');
app.use(cors({
origin: 'https://app.example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
Set origin to your frontend’s exact origin. Do not use a wildcard * in production if you send credentials (cookies or auth headers) โ browsers reject the combination of Access-Control-Allow-Origin: * with credentials: true.
Fix 2: Handle Preflight (OPTIONS) Requests
For any request that is not a simple GET/POST โ for example one that sends Content-Type: application/json or an Authorization header โ the browser first sends a preflight OPTIONS request asking permission. Your server must respond to it with the appropriate headers. The cors middleware above handles this automatically, but if you set headers manually, you must handle OPTIONS explicitly:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://app.example.com');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
return res.sendStatus(204); // Respond to preflight, do not fall through
}
next();
});
A common mistake is failing to respond to the OPTIONS preflight โ the browser sends it, gets no valid CORS response, and blocks the real request before it is ever made.
Fix 3: Match the Origin Exactly
The origin must match precisely โ protocol, domain, and port all count. These are all different origins to the browser:
| Origin | Why It Differs |
|---|---|
| http://localhost:3000 | Different protocol from https |
| https://localhost:3000 | Different protocol from http |
| https://example.com | Different port from :3000 |
| https://www.example.com | www subdomain differs from apex |
If your allowed origin is https://example.com but your app loads from https://www.example.com, CORS blocks it. Allow every origin the app actually uses, or normalize to one.
Fix 4: Use a Dynamic Origin for Multiple Frontends
If several origins need access (staging, production, localhost), validate against an allowlist instead of hardcoding one:
const allowed = [
'https://app.example.com',
'https://staging.example.com',
'http://localhost:3000'
];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowed.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true
}));
Fix 5: For Local Dev, Use a Proxy
During development, you can sidestep CORS by proxying API requests through your dev server so they appear same-origin. In Vite:
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true
}
}
}
};
Now your frontend calls /api/... on its own origin and the dev server forwards it. This is a development convenience, not a production fix โ production still needs proper CORS headers on the API.
What NOT to Do
Do not reach for browser extensions that disable CORS, and do not slap Access-Control-Allow-Origin: * on everything to make the error disappear. The wildcard exposes your API to every site on the internet and breaks credentialed requests. CORS is a security feature โ configure it correctly rather than disabling it.
Frequently Asked Questions
Q: Why does the request work in Postman but not the browser?
A: Postman does not enforce CORS โ only browsers do. The request reaching the server proves the server works; the browser blocks the response because the CORS headers are missing. Fix the headers on the server.
Q: Can I fix CORS from the frontend?
A: No. CORS headers must come from the server responding to the request. The only frontend-side workaround is a dev proxy, which makes requests same-origin during development.
Q: Why does my preflight OPTIONS request fail?
A: The server is not responding to OPTIONS with the correct CORS headers and a 2xx status. Ensure your CORS middleware runs before your routes, or handle OPTIONS explicitly and return 204.
Q: Why does credentials: true break with origin ‘*’?
A: Browsers forbid sending credentials (cookies, auth) when the allowed origin is the wildcard *. Specify the exact origin instead when you need credentials.
Q: The header is there but it still fails. Why?
A: Check that the origin matches exactly (protocol, domain, port), that OPTIONS preflight is handled, and that a proxy or CDN in front is not stripping the header. Inspect the actual response headers in the Network tab.
Conclusion
The “No Access-Control-Allow-Origin header is present” error means the browser blocked a cross-origin response because the server did not permit your origin. The fix is always on the server: send the correct Access-Control-Allow-Origin header, handle the OPTIONS preflight, match the origin exactly, and never use a wildcard with credentials. For local development, a dev proxy avoids the issue cleanly. Once the server returns the right headers for your exact origin and responds to preflight requests, the error disappears and your requests go through.
๐ You might also like
๐ Share this article




โ๏ธ Leave a Comment