Home JavaScript Blocked by CORS Policy: Fixing Failed Browser Requests
Intermediate 6 min · September 23, 2026
CORS Policy Error Fix

Blocked by CORS Policy: Fixing Failed Browser Requests

Browsers block cross-origin responses when the server omits Access-Control-Allow-Origin.

N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Drawn from code that ran under real load.

Follow
Production
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
Before you start⏱ 14 min
  • Basic JavaScript and fetch API knowledge
  • A backend you can configure or inspect
  • Familiarity with browser DevTools
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • The server omitted Access-Control-Allow-Origin, so the browser got the response but hid it from your script. Fix it server-side by returning that header with your exact origin.
  • The request still reaches the server, which is why curl succeeds while fetch fails. Verify in DevTools Network by inspecting the response headers.
  • JSON bodies and custom headers trigger an OPTIONS preflight. Answer it with 2xx plus Access-Control-Allow-Methods and Access-Control-Allow-Headers.
  • With cookies or Authorization headers, use credentials: 'include' and echo one explicit origin. A wildcard * never works with credentials.
✦ Definition~90s read
What is CORS Policy Error Fix?

Blocked by CORS policy is the browser's refusal to expose a cross-origin response to your script. CORS, or Cross-Origin Resource Sharing, is the HTTP header protocol that lets a server grant such permission. The browser compares the page's origin against the response's Access-Control-Allow-Origin value, and when they don't align it discards the body and reports the block.

Picture your browser as a cautious mailroom clerk.

Your code receives a TypeError from fetch, never the status or payload.

The mechanism has three layers. First, the same-origin policy defaults every cross-origin read to denied. Second, simple requests carry an Origin header and get checked on arrival. Third, preflighted requests send an OPTIONS probe describing the intended method and headers, and the real request goes out only after an approved answer.

Credentials mode tightens everything: cookies and Authorization headers travel only with an explicit echoed origin plus Access-Control-Allow-Credentials set to true.

What it is NOT matters just as much. It is not a server error: the server answered normally. It is not a network failure: the bytes arrived intact. It is not a JavaScript bug: identical code works same-origin. It is not a firewall or DNS issue, and fixing it client-side with mode no-cors only hides the response further by making it opaque.

Any tutorial promising a browser-only fix misunderstands the protocol.

Think of it as a bouncer checking wristbands. The club, your server, is open and the music plays. The bouncer, the browser, asks each guest's drink order, your script's request, for a wristband, the CORS headers. No wristband, no drink, even though the bar is fully stocked. Only the club can issue wristbands, which is why every durable fix in this guide changes the server.

Plain-English First

Picture your browser as a cautious mailroom clerk. Your page requests a parcel from another company's warehouse. The clerk fetches it but demands a signed release note naming your company, and that note is the Access-Control-Allow-Origin header. No note, no parcel. Tools like curl skip the clerk and walk into the warehouse themselves, so they succeed where your page fails. The goods were never missing, and only the warehouse can sign the note, which is why every CORS fix happens on the server.

You wire up fetch to your API, the endpoint answers perfectly in curl, and the browser slaps you with red text: blocked by CORS policy. Nothing is wrong with your JavaScript. Nothing is wrong with your URL. The server answered, the bytes arrived, and the browser deliberately threw them away. That moment confuses every web developer exactly once, because the error reads like a network failure while describing a security decision.

The decision belongs to the same-origin policy, the rule that a page may only read responses from its own origin unless the other side explicitly opts in. Scheme, host, and port must all match. Your app at app.example.com calling api.example.com is cross-origin. Your localhost page calling a staging API is cross-origin. Each of those reads needs the server's written permission, delivered as response headers.

This error fires in a handful of repeatable situations. The server omits Access-Control-Allow-Origin. The request triggers an OPTIONS preflight the server never answers. The client sends cookies while the server answers with a wildcard. A proxy strips the headers. Each prints a different console message with its own server-side fix.

This guide walks through all of them. You'll learn what the browser checks, how to read the failing headers in DevTools, why curl keeps lying to you, and how to fix the server in Express, Spring, Nginx, and Django.

Same-Origin Policy Blocks the Read, Never the Request

An origin is the triple of scheme, host, and port. https://app.example.com reads as scheme https, host app.example.com, port 443. Change any one of the three and you have a different origin: http instead of https, api instead of app, or :3001 instead of :443. The same-origin policy says a page may freely read responses from its own origin and nothing else, unless the foreign server explicitly grants permission.

The critical detail is what gets blocked. The policy blocks the read, never the request. When your script calls fetch on a cross-origin URL, the browser sends the HTTP request, the server processes it, and the response comes back over the wire. Then the browser inspects the response headers. Without a matching Access-Control-Allow-Origin value, it refuses to expose the body, status, or headers to your script and reports that the request was blocked by CORS policy. Network-level tools confirm this: the response exists, your code just can't see it.

This design protects authenticated sessions. Without it, any page you visit could fire requests at your bank or webmail with your cookies attached and read the results. The policy lets the request carry credentials but denies the foreign page the response, which closes the data-theft hole while keeping plain navigation working. Simple top-level navigation like clicking a link was never restricted; only script-driven reads are.

CORS is the exemption mechanism, not a separate security layer. The server opts specific origins in by echoing them in Access-Control-Allow-Origin, and the browser enforces that opt-in. No header means no permission, and the default-deny stance is intentional. Every fix in this guide is therefore a server change: you teach the server to grant permission, and the browser stops discarding responses.

origin-check.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
// The browser enforces origin checks; node and curl do not.
// Run this in a browser console on https://app.example.com:
async function checkSameOrigin() {
  const apiOrigin = new URL('https://api.example.com/items').origin;
  console.log('page origin:', window.location.origin);
  console.log('api origin: ', apiOrigin);
  console.log('cross-origin:', window.location.origin !== apiOrigin);
}

checkSameOrigin();
Try it live
⚠ Blocked Reads Can Still Write Data
The request always completes server-side. If your blocked POST creates duplicate orders when retried, that's CORS working as designed colliding with a non-idempotent endpoint. Make mutating endpoints idempotent instead of blaming the browser.
📊 Production Insight
In production this misunderstanding costs real debugging hours. Teams see the CORS message, assume the API is down, and restart healthy services. Check the Network tab first: if the response arrived with a body and status, the server is fine and only the headers need work.
🎯 Key Takeaway
Origin means scheme plus host plus port. The browser sends the request and receives the response, then discards it when permission headers are missing. CORS headers are the server's opt-in, and adding them is the only fix.

Preflight OPTIONS Requests Ask Permission Before Sending Data

Simple requests skip the preflight. A request counts as simple when it uses GET, HEAD, or POST with only safelisted headers like Accept or Content-Type limited to form-like values. Everything else triggers a preflight: methods like PUT, PATCH, or DELETE, any custom header such as Authorization or X-Request-Id, and content types like application/json. Since most real APIs speak JSON, most real API calls get preflighted.

The preflight is an OPTIONS request the browser sends before your actual call. It carries Origin plus two questions: Access-Control-Request-Method naming the method you want, and Access-Control-Request-Headers listing your custom headers. The server must answer with a 2xx status and its own permissions: Access-Control-Allow-Methods listing the methods it accepts and Access-Control-Allow-Headers listing the headers it accepts. Only when that answer satisfies the browser does it send your real request.

Three server mistakes break this handshake. Returning 404 or 405 for OPTIONS because no route handles it. Answering 401 because auth middleware rejects a request that carries no credentials by design. Or returning 200 with no CORS headers because the middleware that adds them runs after the OPTIONS handler returns. Any of these makes the browser cancel the real request and blame CORS.

Preflight answers are cached for the window given by Access-Control-Max-Age, so one OPTIONS exchange covers minutes of subsequent calls. During development that cache hides your fixes, which is why seasoned developers test with DevTools cache disabled. In production the cache is your friend: it keeps chatty single-page apps from doubling every API call.

preflight-post.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
// A JSON POST triggers a preflight; a plain form POST does not.
async function createItem() {
  const res = await fetch('https://api.example.com/items', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: 'lamp', qty: 2 })
  });
  if (!res.ok) throw new Error('request failed: ' + res.status);
  return res.json();
}

createItem().then(console.log).catch(console.error);
Try it live
📊 Production Insight
Auth-gated preflights are the most common production cause. The OPTIONS request carries no credentials by design, so any middleware that 401s it kills every authenticated cross-origin call. Exempt OPTIONS from auth at the edge and the whole category disappears.
🎯 Key Takeaway
JSON bodies, custom headers, and non-GET verbs trigger an OPTIONS preflight. The server must answer 2xx with Allow-Methods and Allow-Headers, and auth middleware must let preflights through unauthenticated.

Allowed Origins, Wildcards, and the Credentials Trap

Access-Control-Allow-Origin accepts exactly one origin or a wildcard, never a list. To serve several frontends, the server reads the request's Origin header, checks it against an allowlist, and echoes the match back verbatim. That echo must be byte-exact: https://app.example.com is not http://app.example.com, and a trailing slash breaks the match. Pair the echo with Vary: Origin so shared caches store one response per origin instead of serving site A's permission to site B.

Credentials mode changes the rules. Fetch defaults to same-origin, meaning cookies travel only within your own origin. Cross-origin cookie calls need credentials set to include, and XMLHttpRequest needs withCredentials set true. The moment credentials travel, the wildcard becomes illegal: the browser rejects any credentialed response whose allowed origin is *. The server must echo your exact origin and add Access-Control-Allow-Credentials: true. Miss either half and the browser blocks the read.

This pairing is deliberately strict. A wildcard says any site on earth may read the response, while credentials attach the user's session to it. Allowing both would let a phishing page read your authenticated data with one fetch call. The browser refuses the combination to keep that hole shut, which is why credentialed CORS demands explicit origins.

A subtler trap is reflecting arbitrary origins. Echoing whatever Origin arrives without validation feels like a fix, but it grants every malicious site full read access with credentials. Validate against a fixed allowlist of your own frontends, reject everything else with no CORS headers, and log rejected origins so you notice probing early.

credentialed-fetch.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
// Credentialed fetch needs an explicit echoed origin server-side.
async function loadProfile() {
  const res = await fetch('https://api.example.com/profile', {
    credentials: 'include' // sends cookies cross-origin
  });
  if (!res.ok) throw new Error('request failed: ' + res.status);
  return res.json();
}

loadProfile().then(console.log).catch(console.error);
Try it live
⚠ Wildcards and Credentials Never Mix
Never reflect the request Origin without checking it against an allowlist. Blind reflection turns your CORS config into a universal reader for any phishing page, defeating the protection credentials mode is meant to keep.
📊 Production Insight
Multi-tenant SaaS hits this hardest: each customer brings a custom domain that must join the allowlist. Store allowed origins per tenant in config, not code, and reject unknown origins silently. One customer onboarding should never require a code deploy.
🎯 Key Takeaway
Echo one validated origin plus Vary: Origin for multi-frontend APIs. Credentialed requests need credentials include client-side and an explicit origin with Allow-Credentials true server-side. Never reflect unvalidated origins.

Why curl Succeeds While Your Browser Fails

curl, Postman, server-side code, and mobile apps never implement the same-origin policy, so they never produce CORS errors. They send the request and show you the raw response, headers and all. That makes them excellent for testing APIs and terrible for testing CORS. A green curl result proves the endpoint works; it says nothing about whether a browser will let your page read the answer.

The mismatch misleads because the failure lives in the browser, not the server. Your Express route returns JSON with a 200, curl prints it, and you conclude the backend is innocent. Meanwhile the response lacks Access-Control-Allow-Origin, so every browser discards it. Both observations are true at once: the server responds correctly and the browser blocks correctly. The bug is the missing permission header between them.

To make curl useful, force it to act like a browser by attaching an origin: curl with an Origin header plus verbose output shows exactly which Access-Control headers return. For preflights, send OPTIONS with Access-Control-Request-Method and Access-Control-Request-Headers and watch the status and Allow headers. Those two commands reproduce any browser failure from a terminal, which matters when the failing client is a customer's laptop you can't inspect.

Treat the browser as the source of truth and curl as the lab instrument. Confirm the failure in DevTools Network, reproduce it with origin-carrying curl, fix the server, then verify in both. Teams that skip the curl repro end up screen-sharing with users instead of testing headers themselves.

📊 Production Insight
On-call engineers love curl and reach for it first, which adds a wasted round trip to every CORS page. Put the origin-carrying curl one-liner in your runbook so the first responder reproduces the browser's view instead of proving the server is alive.
🎯 Key Takeaway
Only browsers enforce CORS, so plain curl always succeeds. Reproduce with an Origin header attached, treat DevTools as the source of truth, and verify every fix in both tools.

Server Fixes in Express, Spring, Nginx, and Django

Every fix below does the same thing: return the right headers for your origins. In Express, the cors middleware handles preflights and echoes origins when you pass a validation function plus credentials true. The function form matters: a static string can't serve several frontends, and a wildcard breaks credentialed calls. Place the middleware before auth and body parsing so OPTIONS gets answered before anything else can reject it.

In Spring, annotate the controller with CrossOrigin and name your origins, methods, and headers explicitly: origins set to your frontend, allowedHeaders including Authorization and Content-Type, allowCredentials set to true. The annotation defaults are permissive in the wrong ways, so spell every attribute out. For global coverage, register the same mapping once in a WebMvcConfigurer instead of annotating each controller.

In Nginx, add the headers in the location block serving the API and use the always flag so error responses keep them: Access-Control-Allow-Origin with your origin, Access-Control-Allow-Credentials true, and Vary Origin. Answer OPTIONS with a 204 and the Allow-Methods and Allow-Headers lines, then return before proxying. Without always, every 4xx and 5xx leaves headerless and the browser reports CORS instead of the real status.

In Django, install django-cors-headers, put its middleware first, and set CORS_ALLOWED_ORIGINS to your frontend list with CORS_ALLOW_CREDENTIALS true. The defaults deny everything, which surprises teams that install the package and change nothing. Middleware order decides everything here too: if auth or error handling runs first, preflights die before CORS headers attach.

server.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const express = require('express');
const cors = require('cors');

const app = express();
const allowed = new Set(['https://app.example.com']);

app.use(cors({
  origin: (origin, done) => done(null, !origin || allowed.has(origin)),
  credentials: true
}));
app.use(express.json());

app.get('/items', (req, res) => res.json([{ id: 1 }]));
app.listen(3000, () => console.log('api on :3000'));
Try it live
📊 Production Insight
Middleware order causes more CORS outages than wrong values. Audit the chain in each stack so CORS runs before auth, and add a deploy-time check that OPTIONS returns 204 with the Allow headers. That single probe catches most regressions before users do.
🎯 Key Takeaway
Echo validated origins with credentials support in every stack: cors middleware in Express, explicit CrossOrigin in Spring, add_header always in Nginx, first-position middleware in Django. Handle OPTIONS before auth everywhere.

Debugging CORS With DevTools Network and Console

Start in the console, finish in the Network tab. The console message names the failed check: a missing Allow-Origin, a disallowed method, a rejected header, or a wildcard paired with credentials. That sentence tells you which header to hunt. But the console never shows header values, so click the failed request in the Network tab and read its Response Headers directly. The missing or wrong line is usually obvious within seconds.

For preflighted calls, look one row above the failed request for the OPTIONS call. Its status and response headers tell the preflight story: 401 means auth blocked it, 404 means no route handles OPTIONS, 200 without Allow headers means the CORS layer never ran. If no OPTIONS row exists, the request was simple and the failure is purely about the response's Allow-Origin value.

Status codes deserve a second look because the browser hides them. When the server returns 500 without CORS headers, the console reports a CORS error and the Network tab may show the request as failed, burying the real 500. Check server logs for the same timestamp before trusting the CORS label. More than one team has tuned CORS for an hour while the database was down.

Finish by verifying the fix in a fresh profile or with cache disabled. Preflight caching means a corrected server can still look broken until the old negative result expires. A clean reload takes seconds and removes the one variable that makes fixed bugs look alive.

cors-diagnose.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Diagnose a failing call from the browser console.
async function diagnose(url) {
  try {
    const res = await fetch(url, { credentials: 'include' });
    console.log('status:', res.status);
    console.log('acao:', res.headers.get('content-type'));
    return res.json();
  } catch (err) {
    console.error('blocked or failed:', err.message);
    console.error('open DevTools Network, click the request,');
    console.error('and read its Response Headers.');
    throw err;
  }
}

diagnose('https://api.example.com/profile').then(console.log);
Try it live
📊 Production Insight
Teach support this exact sequence and CORS tickets get resolved without engineering. Console message plus Network headers plus the OPTIONS row answers nearly every report, and the cache-disabled retest closes the loop on stale preflights.
🎯 Key Takeaway
Read the failed check in the console, the header values in the Network tab, and the OPTIONS row above for preflights. Cross-check server logs for hidden 5xx statuses and re-test with cache disabled.
● Production incidentPOST-MORTEMseverity: high

Sale-Night Checkout Failures That Wore a CORS Disguise for 38 Minutes

Symptom
At 7:40 PM on a sale night, checkout success dropped from 98% to 61% for 38 minutes. Browsers showed CORS errors on the fraud-verification call. Backend latency, CPU, and error-rate dashboards looked normal because 429s weren't counted as failures.
Assumption
The team assumed CORS was settled because every 200 response carried the right headers and staging had passed. Nobody had tested what the headers looked like on a 429, and the rate-limit threshold had never been hit in staging traffic.
Root cause
Nginx added CORS headers with plain add_header directives and no always flag, so 200 responses carried them but the fraud service's 429 responses did not. When checkout traffic peaked at 210 verification calls per minute against a 60-per-minute limit, browsers received headerless 429s and reported blocked by CORS policy instead of Too Many Requests. Engineers spent 25 minutes rechecking CORS config before backend logs revealed the 429 flood.
Fix
The Nginx config gained the always flag on all three CORS directives: add_header Access-Control-Allow-Origin $http_origin always, Access-Control-Allow-Credentials true always, and Vary Origin always. The fraud-check limit rose from 60 to 300 verifications per minute per API key, matching the checkout peak of 210 per minute with headroom. A synthetic monitor now POSTs an invalid coupon every 5 minutes and alerts if the 422 response lacks CORS headers.
Key lesson
  • Test CORS on error responses, not just the happy path. A missing always flag turns every backend failure into a misleading browser error that sends debugging in the wrong direction.
  • Rate limits need load-shaped staging tests. A threshold nobody ever trips in staging is a threshold you'll discover in production at the worst hour.
  • Monitor the headers, not just the status codes. A 5-minute synthetic check against an error endpoint would have caught this the day it deployed.
Production debug guideFive checks that isolate the missing header in minutes, with the exact commands for each.5 entries
Symptom · 01
Console shows the CORS error but not which header failed
Fix
Open DevTools, run the failing call, and click it in the Network tab. Read the Response Headers: is Access-Control-Allow-Origin absent, a wildcard paired with cookies, or missing your custom header? The console names the failed check, but only the headers show the truth. Compare against a working endpoint to spot the difference line by line.
Symptom · 02
Plain curl returns 200 while the browser keeps failing
Fix
Send the same request with an origin attached: curl -H 'Origin: https://app.example.com' -v https://api.example.com/items. The -v output shows exactly which Access-Control headers come back. Then repro the preflight directly: curl -X OPTIONS -H 'Origin: https://app.example.com' -H 'Access-Control-Request-Method: POST' -H 'Access-Control-Request-Headers: content-type' -v https://api.example.com/items. A missing or non-2xx answer here is your bug.
Symptom · 03
Requests with cookies fail while anonymous calls succeed
Fix
Check the request for credentials: include in fetch or withCredentials in XHR. If credentials travel, the server must echo your exact origin and add Access-Control-Allow-Credentials: true. A wildcard origin is rejected by every browser in this mode. Fix the server config, then hard-reload with cache disabled so a stale preflight doesn't fool you.
Symptom · 04
CORS passes on success but fails on API errors
Fix
Verify the failing status code: curl -H 'Origin: https://app.example.com' -o /dev/null -w '%{http_code}' https://api.example.com/items. If CORS fails only on 4xx or 5xx, your Nginx add_header lines lack the always flag or your framework adds headers only on success. Add always, redeploy, and re-test the error path with nginx -t && nginx -s reload after a config test.
Symptom · 05
Headers exist on the origin server but vanish in the browser
Fix
Bypass each hop in turn: call the origin server directly, then through the CDN or gateway, each time with curl -H 'Origin: ...' -v. The hop where Access-Control headers vanish is stripping them. Common culprits are response caching without Vary: Origin and header allowlists that drop unknown headers. Add Vary: Origin and forward the CORS headers at that layer.
CORS Policy Error Causes Compared
Root CauseHow to ConfirmFixPrevention
Missing Access-Control-Allow-Origin on the responseDevTools Network shows no ACAO header on the failed responseReturn the requesting origin in ACAO from the serverAdd a CORS integration test that asserts the header on every endpoint
OPTIONS preflight rejected or unansweredA failed OPTIONS request precedes the blocked call in the Network tabAnswer OPTIONS with 2xx plus Allow-Methods and Allow-HeadersKeep auth middleware from gating preflights; test POST and PUT, not just GET
Wildcard origin combined with credentialsACAO is * while the request carries cookies or AuthorizationEcho one explicit origin and set Allow-Credentials: trueLint configs for a wildcard paired with credentials support
Proxy or CDN strips CORS headersHeaders present hitting the origin directly but missing through the proxyForward or re-add CORS headers at the proxy layerRun header checks through the full proxy chain in staging
Custom header missing from Allow-HeadersConsole names the exact header the preflight disallowedAdd the header name to Access-Control-Allow-HeadersLog preflight decisions so new headers get allowlisted before launch
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
origin-check.jsasync function checkSameOrigin() {Same-Origin Policy Blocks the Read, Never the Request
preflight-post.jsasync function createItem() {Preflight OPTIONS Requests Ask Permission Before Sending Dat
credentialed-fetch.jsasync function loadProfile() {Allowed Origins, Wildcards, and the Credentials Trap
server.jsconst express = require('express');Server Fixes in Express, Spring, Nginx, and Django
cors-diagnose.jsasync function diagnose(url) {Debugging CORS With DevTools Network and Console

Key takeaways

1
CORS blocks the browser from reading the response, never the request itself.
2
The fix is always server-side
return the right Access-Control headers.
3
Preflighted requests need an OPTIONS answer with methods and headers allowed.
4
A wildcard origin is rejected whenever credentials travel with the request.
5
curl and Postman skip CORS, so only the browser or an Origin-carrying curl repro counts.
6
Test error paths too, since missing headers on 4xx and 5xx responses mask real failures.

Common mistakes to avoid

6 patterns
×

Setting Access-Control-Allow-Origin: * while sending credentials

Symptom
Simple requests succeed, but every credentialed fetch fails with a CORS error even though the header is present.
Fix
Return one explicit origin that matches the request's Origin, or maintain a vetted allowlist and echo back the match. Add Vary: Origin so caches don't serve one site's CORS headers to another.
×

Protecting the OPTIONS preflight with authentication

Symptom
GET works but POST with a JSON body fails; the preflight returns 401 and the real request never leaves the browser.
Fix
Handle OPTIONS in the same layer that adds CORS headers, return 204 with Allow-Methods and Allow-Headers, and keep auth middleware from rejecting preflights before they reach it.
×

Using Nginx add_header without the always flag

Symptom
CORS works on 200 responses but fails the moment the API returns a 4xx or 5xx, masking the real error behind a CORS message.
Fix
Add the always flag to every add_header CORS directive and test an error path, not just the happy path. Mirror the config in staging so header behavior matches production exactly.
×

Forgetting Access-Control-Allow-Headers for custom headers

Symptom
The preflight returns 204 yet the browser still blocks the request, naming the specific header that isn't allowed.
Fix
List every non-standard header the client sends, including Authorization and any X- prefixed names, in Access-Control-Allow-Headers. Mirror the exact spelling the browser sends.
×

Debugging CORS with plain curl and no Origin header

Symptom
curl returns 200 with a clean body, so the team concludes the server is fine while every browser keeps failing.
Fix
Reproduce with curl carrying an Origin header, or log the response headers on the failing request itself. Reserve the console for confirming which check failed after you've seen the headers.
×

Leaving fetch credentials at the default while the API needs cookies

Symptom
Login succeeds but every later call acts logged-out; cookies never travel because same-origin is the default credential mode.
Fix
Change credentials to omit for public endpoints, or echo a single origin and set credentials to include only where cookies are truly needed. Never mix a wildcard with credentialed requests.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does the same-origin policy protect, and how does CORS relax it?
Q02SENIOR
When does the browser send a preflight, and what must the server return?
Q03SENIOR
Why can't Access-Control-Allow-Origin: * be combined with credentials?
Q04JUNIOR
Your API works in curl but fails in the browser. What is your first move...
Q05SENIOR
CORS passes on success but fails on every API error. What is the likely ...
Q01 of 05JUNIOR

What does the same-origin policy protect, and how does CORS relax it?

ANSWER
The same-origin policy stops a page from reading responses that belong to another origin, where origin means scheme plus host plus port. Without it, any site you visit could call your bank's API with your cookies and read the balance. CORS is the controlled exemption: the server opts in with headers, and the browser enforces them.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Does a CORS error mean the request never reached the server?
02
Can I list several origins in one Access-Control-Allow-Origin header?
03
Why does a wildcard origin fail when I send cookies?
04
How long does the browser cache a preflight result?
05
Do mobile apps and backend services face CORS errors?
06
What happens when a cross-origin request redirects?
N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Drawn from code that ran under real load.

Follow
Verified
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
🔥

That's Advanced JS. Mark it forged?

6 min read · try the examples if you haven't

Previous
Tailwind CSS v4 Migration Guide
28 / 28 · Advanced JS
Next
ECONNREFUSED Connection Refused Fix