CORS Error — Access-Control-Allow-Origin Missing or Mismatched
CORS: Access-Control-Allow-Origin error
Verified against Fetch Standard (WHATWG): CORS protocol, MDN Web Docs: Cross-Origin Resource Sharing, RFC 6454 (The Web Origin Concept) · Updated June 2026
> quick_fix
The browser is blocking the request because the server's response is missing the `Access-Control-Allow-Origin` header matching your frontend's origin. The fix is always on the server — add the correct CORS headers. Never use `*` in production with credentials.
# Test if the server sends the correct CORS headers
curl -si https://api.example.com/endpoint \
-H 'Origin: https://yourapp.com' \
-H 'Access-Control-Request-Method: POST' \
-X OPTIONS
# You need to see:
# Access-Control-Allow-Origin: https://yourapp.com
# Access-Control-Allow-Methods: GET, POST, OPTIONS
# Access-Control-Allow-Headers: Content-Type, AuthorizationWhat causes this error
CORS (Cross-Origin Resource Sharing) is enforced by browsers — not servers, not curl, not Postman. When your frontend at `https://app.example.com` calls an API at `https://api.example.com`, the browser checks that the API's response includes `Access-Control-Allow-Origin: https://app.example.com`. If the header is missing, wrong, or `*` when credentials are needed, the browser blocks the response and logs a CORS error.
How to fix it
- 01
step 1
Add CORS headers on the server — not the client
CORS is a server-side concern. Adding headers in your browser-side JavaScript does nothing. The server must include the correct `Access-Control-Allow-Origin` header. For Express, use the `cors` package. For Next.js App Router, use `next.config.js` headers or route handler headers.
// Express — cors package (recommended) const cors = require('cors') app.use(cors({ origin: ['https://app.example.com', 'https://staging.example.com'], methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, // only if using cookies/auth headers })) // Handle OPTIONS preflight explicitly app.options('*', cors()) - 02
step 2
Handle preflight OPTIONS requests correctly
The browser sends an OPTIONS preflight request before any non-simple cross-origin request. The server MUST respond to OPTIONS with a 200 (or 204) and the CORS headers — without requiring authentication. If auth middleware runs before CORS middleware, it blocks the preflight with 401, causing a CORS error.
// Middleware order matters — CORS before auth app.use(cors({ origin: 'https://app.example.com' })) // 1st app.use(authMiddleware) // 2nd app.use(express.json()) // 3rd // BAD order (auth blocks OPTIONS preflight): app.use(authMiddleware) // blocks preflight! app.use(cors({ origin: 'https://app.example.com' })) - 03
step 3
Never use Access-Control-Allow-Origin: * with credentials
If your request includes cookies or an Authorization header (`credentials: 'include'` in fetch, or `withCredentials: true` in axios), the server MUST respond with the specific origin — not `*`. Using `*` with credentials causes the browser to block the response.
// Browser — request with credentials fetch('https://api.example.com/me', { credentials: 'include', // sends cookies }) // Server MUST respond with the specific origin, not * // ✅ Access-Control-Allow-Origin: https://app.example.com // ✅ Access-Control-Allow-Credentials: true // ❌ Access-Control-Allow-Origin: * (blocked by browser with credentials) - 04
step 4
Fix CORS in Next.js App Router
Next.js API routes don't add CORS headers by default. Add them in the route handler or via `next.config.js` headers for the API path.
// app/api/data/route.ts export async function GET(req: Request) { return new Response(JSON.stringify({ data: 'ok' }), { headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': 'https://app.example.com', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }, }) } // Handle preflight export async function OPTIONS() { return new Response(null, { status: 204, headers: { 'Access-Control-Allow-Origin': 'https://app.example.com', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }}) } - 05
step 5
Add CORS headers at the Nginx/proxy level
If you can't change the application code (e.g., proxying a third-party service), add CORS headers at the Nginx level. This works for simple cases but doesn't support dynamic origin reflection.
# Nginx — add CORS headers for all responses location /api/ { proxy_pass http://backend; add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always; if ($request_method = OPTIONS) { add_header 'Access-Control-Max-Age' 86400; return 204; } }
How to verify the fix
- curl with `-H 'Origin: https://yourapp.com'` shows `Access-Control-Allow-Origin` matching your origin.
- Browser Network tab shows the actual API response (not blocked).
- Requests with credentials (`credentials: 'include'`) receive `Access-Control-Allow-Credentials: true`.
Why CORS happens at the runtime level
CORS is implemented in the browser's fetch/XHR layer per the WHATWG Fetch Standard. After receiving a cross-origin response, the browser inspects `Access-Control-Allow-Origin`. If it's absent or doesn't match the requesting origin, the browser prevents JavaScript from reading the response body and throws a network error. The server did respond — the browser just won't let your code see it. This design prevents malicious websites from using a logged-in user's cookies to read data from their bank or GitHub account. The restriction applies only to the browser's fetch/XHR — `<img src>`, `<script src>`, and `<link>` tags are not subject to CORS (they use a different security model).
Common debug mistakes for CORS
- Returning `Access-Control-Allow-Origin: *` when the request includes `credentials: 'include'` — browsers reject this combination.
- CORS middleware placed after authentication middleware — OPTIONS preflight gets 401 and the browser reports a CORS error.
- Using `http://` for the origin in production when the frontend is served over `https://` — protocol is part of the origin.
- Forgetting to handle the OPTIONS method — some frameworks require explicit OPTIONS route handlers.
- Reflecting the `Origin` header back without validating it against an allowlist — creates a security vulnerability where any origin gets access.
When CORS signals a deeper problem
CORS errors on third-party APIs you don't control are a sign of missing backend-for-frontend (BFF) infrastructure. You should never call third-party APIs directly from browser JavaScript — the API keys end up in client-side code (visible in DevTools), and you can't add CORS headers to a server you don't own. The fix is a thin backend proxy: your frontend calls your own API, which calls the third-party API server-to-server. This keeps API keys secure, gives you CORS control, and allows you to add caching and rate limiting. If you're hitting CORS on every third-party integration, you don't have a CORS problem — you have an architecture problem.
Editor's take
CORS is the error that every frontend developer hits within their first month and spends the next year explaining to teammates. The frustration comes from the error message being deliberately unhelpful — 'CORS policy: No Access-Control-Allow-Origin header' tells you what's wrong but not why. The actual why is: you're making a cross-origin request (different domain, port, or protocol), the server didn't opt in to allow it, and the browser blocked it.
The most expensive version of this error is the production CORS bug that only affects certain users. The server is configured with a static `Access-Control-Allow-Origin: https://app.example.com`, but the mobile team ships a WebView that loads from `https://app.example.com` on iOS but from `capacitor://localhost` on Android. The Android WebView gets CORS errors because `capacitor://localhost` isn't in the allowlist. The fix is reflecting the Origin header dynamically from a validated allowlist — but you have to know that capacitor:// is an origin, which requires reading the Capacitor docs carefully.
The adjacent security issue worth understanding: CORS misconfiguration is consistently in the OWASP Top 10. A server that reflects any Origin back without validation (`Access-Control-Allow-Origin: <incoming_origin>` for all requests) is effectively disabling the protection CORS provides. An attacker can craft a page at `https://attacker.com` that makes fetch requests to your API with the user's credentials, reads the response, and exfiltrates user data. Always validate incoming Origin values against an explicit allowlist before reflecting them.
By Bikram Nath · Curator · Updated June 2026
Frequently asked questions
Why does the request work in Postman but fail in the browser?
CORS is enforced by browsers only. Postman, curl, and server-to-server calls bypass CORS entirely. The browser is the only client that checks for the `Access-Control-Allow-Origin` header. So 'works in Postman, fails in browser' is the normal diagnosis for a CORS error — it's not a bug in Postman, it's how CORS is designed to work.
What is a preflight request and why is it needed?
Before sending a cross-origin request with a non-standard method (anything other than GET/POST with simple headers), the browser sends a preflight OPTIONS request asking the server: 'Will you accept a POST from this origin with these headers?' The server's OPTIONS response tells the browser whether to proceed. This prevents cross-origin form submissions and API calls that could cause side effects without the server's explicit opt-in.
Can I disable CORS in Chrome for development?
Yes, but don't. Running Chrome with `--disable-web-security` or using a CORS bypass browser extension hides real CORS misconfigurations that will bite you in production. Instead, configure CORS correctly on your local dev server (accept `http://localhost:3000` as a valid origin) or use a local reverse proxy. The 5 minutes it takes to configure CORS locally saves the 2-hour debugging session when it breaks in production.