Guide · Backend
CORS Explained: Why the Browser Blocks Your Request
25 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- CORS (Cross-Origin Resource Sharing) is how a server tells the browser which other websites may read its responses. Without that permission, the browser hides the response from the page's code.
- An origin is the scheme (http or https), host and port a page came from. The same-origin policy is the browser rule that a page may freely read responses only from its own origin.
- A preflight is an automatic OPTIONS request the browser sends first to ask the server for permission. Labelling a body as JSON (the text format most APIs use for data) with Content-Type: application/json triggers one. So do custom headers (extra request headers you name yourself) and methods such as PUT or DELETE.
- Only browsers enforce CORS, so the same call works in curl, a command-line HTTP tool, and fails on a web page. Fix it on the server with an allowlist, a fixed list of exact origins you trust.
- Beginners can stop after the section on fixing CORS errors. Engineers then get details we tested in Chromium 141, a 2025 build of the open-source browser behind Chrome and Edge. Preflight answers are remembered per URL, safe headers have size limits, and browsers bend the standard on the Authorization header.
What goes wrong when a web page calls an API on another domain?
Unless the API's reply says that page may read it, the browser hides the reply from the page's code, and some requests are never sent at all. The console shows an error ending in "blocked by CORS policy", while the same call works in curl.
Picture a common setup. A paint company builds a colour-picker widget: a shopper uploads a photo of a wall, taps a shade and sees the wall painted. About 340 paint dealers paste one line of code into their own websites to show it. The setup is made up, but the problem is real.
The widget runs inside each dealer's page, but it fetches shades and prices from the company's colour API, on the company's own domain. Version 1 asked for shades with a plain GET request, and it worked everywhere.
Version 2 also saves the shopper's chosen shades. It sends them as JSON, the text format most APIs use for data. It also adds a widget key header to every call, including the one that loads shades, so the API team can tell dealers apart. Within an hour, dealers start calling.
On 212 of the 340 sites, the ones that had already picked up version 2, the palette is blank. The developer console shows red lines saying the request was blocked by CORS policy.
The API engineer tests the same endpoint with curl, a command-line tool for sending HTTP requests, and it returns shades perfectly. Nothing in the server's error log explains it. The blocker is the shopper's browser, and the rest of this guide explains what it was asking for.
What is an origin, and why does the browser care about it?
An origin is the combination of scheme, host and port that a page was loaded from. The browser uses it to decide which code may read which data.
The scheme is the protocol, such as https. The host is the domain name, such as the dealer's shop domain. The port is a number that picks a service on that host, by default 443 for https. RFC 6454, the web standard for origins, says two origins are the same only when all three are identical.
- Same origin: two pages on the dealer's domain, both over https on port 443, with different paths. The path does not count.
- Different origin: the dealer's page and the colour API. The hosts differ.
- Different origin: the dealer's domain with and without www in front. A subdomain is a different host.
- Different origin: the same domain over http and over https. The schemes differ.
- Different origin: a test page on localhost port 3000 and an API on localhost port 8000. The ports differ.
Browsers follow a rule called the same-origin policy. Code on a page may freely read responses only from its own origin. It can still send many requests elsewhere, show images and run scripts from elsewhere. It just cannot read another origin's response data.
The reason is your logged-in sessions. Your browser holds cookies for your bank, your email and your office tools. Without this rule, any page you visit could quietly call those sites as you and read the replies.
What is CORS, and who actually enforces it?
CORS, short for Cross-Origin Resource Sharing, is a set of HTTP headers that lets a server tell the browser which other origins may read its responses. The server declares the policy; the browser enforces it.
A header is a small named line of extra information sent with a request or a response. In CORS, the browser adds an Origin header saying where the calling page came from. The server answers with headers saying who may read the reply.
Picture a members-only gymkhana club. A guest cannot walk in alone. At the gate, the guard checks whether a member has signed this exact guest in. If a member has, the guest goes in; if not, the guest waits outside.
Map it part by part. The browser is the gate guard. The page's code is the guest. The colour API is the member, and its response headers are the signature at the gate. The guard does not judge the guest; it only checks whether the member vouched for them.
Here is the widget setup. Notice that the widget's code runs inside the dealer's page, so its origin is the dealer's origin, even though the code file came from the paint company.
Where the widget lives and what it calls
Drag to exploreNotice the script file comes from the paint company but runs on the dealer's origin, so every API call is cross-origin.
Diagram connections:
- Shopper's browser to Dealer's website (Loads the page)
- Dealer's website to Widget script file (Includes the widget)
- Widget script file to Colour API (Cross-origin fetch)
- Colour API to Shades and dealer table (Reads shades)
- Shopper's browser: Enforces CORS on every widget call
- Dealer's website: Page origin: the dealer's own domain
- Widget script file: Served by the paint company, runs in the dealer page
- Colour API: Different origin: the paint company's domain
- Shades and dealer table: Shades, prices and allowed dealer origins
A common beginner belief is that the origin is wherever a script file was downloaded from. It is not. The origin belongs to the page that runs the code, so the API must allow each dealer's origin, not its own.
Why does an API call work in curl but fail with a CORS error in the browser?
It works in curl because only browsers enforce CORS. Curl, native mobile app code and server code do not apply the same-origin policy, so they never ask for CORS permission.
So the browser blocks your request, not the server. The server usually works fine. The browser is protecting the person using it, and it needs the server's permission before it hands the response to the page.
For a simple request, which a later section defines, the call reaches the server and the server replies. Then the browser reads the reply's CORS headers. If they do not allow the page's origin, the page's code never sees the reply.
Follow the arrows below to the decision box. The No branch is what the 212 dealer sites hit.
A simple cross-origin GET for shades
Drag to exploreNotice the server answers either way. Only the browser decides whether the page may read the answer.
Diagram connections:
- Widget asks for shades to Browser adds Origin header
- Browser adds Origin header to API sends shades back
- API sends shades back to Origin allowed in reply?
- Origin allowed in reply? to Widget reads the shades (Yes)
- Origin allowed in reply? to Reply hidden, error shown (No)
- Widget asks for shades: Widget code → Browser
- Browser adds Origin header: Browser → Colour API
- API sends shades back: Colour API → Browser
- Origin allowed in reply?: Browser → Browser
- Widget reads the shades: Browser → Widget code
- Reply hidden, error shown: Browser → Developer console
Two details trip people up. First, the page's code gets only a vague error. MDN, the web documentation project, says the specifics are not available to JavaScript for security reasons; only the console shows them.
In our test in Chromium 141 (the open-source browser behind Chrome and Edge), the page's code got TypeError: Failed to fetch, a generic error with no reason. The console said: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Second, a CORS error does not mean the server did nothing. In the same test, a blocked text/plain POST and a blocked form POST both reached the server, which logged them. CORS controls who may read the reply, not whether a simple request runs.
What is a CORS preflight request, and when does the browser send one?
A preflight is an automatic OPTIONS request the browser sends before a non-simple cross-origin request, asking whether the real request is allowed. The real request is sent only if the answer allows it.
OPTIONS is an HTTP method, like GET or POST, that asks a server what it supports. In a preflight, the browser sends the page's Origin, the method it wants to use, and the names of the extra headers it wants to send.
Back at the gymkhana. A guest who only drops off a parcel at the front desk is checked on the spot. A guest who wants to bring four friends into the pool is different: the guard phones the member first.
When does a request skip the preflight?
A request skips the preflight only when it looks like something an old HTML form could already send. MDN calls these simple requests and lists the conditions; all must hold.
- The method is GET, HEAD or POST.
- The page sets by hand only safelisted headers: Accept, Accept-Language, Content-Language, Content-Type, and Range with a single range.
- Content-Type, if set, is application/x-www-form-urlencoded, multipart/form-data or text/plain. A charset, as in text/plain;charset=utf-8, is fine.
- The request uses no streaming body (one sent in pieces as it is produced), and no upload progress listener on the older XMLHttpRequest API.
Pause and predict. Version 2 of the widget sends one POST with a JSON body labelled application/json and a widget key header. How many requests reach the API, and in what order?
The answer is two: an OPTIONS preflight first, then the POST. The JSON Content-Type breaks one rule and the custom X-Widget-Key header breaks another. In our Chromium test, either change alone triggered a preflight.
If the preflight answer is wrong, only one request arrives. Our test server logged a lone OPTIONS, and the POST was never sent. Look at the second and third boxes below: they happen before the real request leaves the browser.
Preflight before saving a shopper's shades
Drag to exploreNotice two requests reach the API for one save, and the POST waits for the OPTIONS answer.
Diagram connections:
- Widget wants to POST JSON to Send OPTIONS preflight
- Send OPTIONS preflight to Answer with allow headers
- Answer with allow headers to Remember the answer
- Remember the answer to Send the real POST
- Send the real POST to Save shades, reply
- Widget wants to POST JSON: Widget code → Browser
- Send OPTIONS preflight: Browser → Colour API
- Answer with allow headers: Colour API → Browser
- Remember the answer: Browser → Preflight cache
- Send the real POST: Browser → Colour API
- Save shades, reply: Colour API → Widget code
What just happened at the paint company? The API already sent Allow-Origin on its GET replies, which is why version 1 worked. But it had never handled OPTIONS. Its key check ran first and answered 401, because a preflight carries only header names, never their values or cookies.
A preflight needs a reply with a 2xx status and the right headers. The Fetch Standard, the browser specification behind fetch and CORS, calls this an ok status: 200 to 299. In our test, a 403 preflight reply blocked the call even with correct headers.
Every preflight is an extra round trip. The Access-Control-Max-Age header tells the browser how many seconds to remember the answer. Without it, the Fetch Standard uses 5 seconds; in our test, a repeat call 6 seconds later sent a fresh preflight.
MDN says Firefox caps Max-Age at 24 hours (86,400 seconds) and Chromium-based browsers at 2 hours (7,200 seconds). Larger values give no extra benefit in those browsers.
Which CORS headers does the server need to send?
The server must send Access-Control-Allow-Origin with the caller's exact origin, and answer preflights with the methods and headers it accepts. A few more headers cover cookies, caching and readable response headers.
| Header | What it tells the browser | Colour API sends |
|---|---|---|
| Access-Control-Allow-Origin | Which one origin may read this reply, or * for any | The dealer's exact origin, from the allowlist |
| Access-Control-Allow-Methods | Which methods are allowed (preflight reply) | GET, POST |
| Access-Control-Allow-Headers | Which request headers are allowed (preflight reply) | Content-Type, X-Widget-Key |
| Access-Control-Max-Age | How many seconds to remember the preflight answer | 7200, the Chromium cap |
| Access-Control-Allow-Credentials | Whether a reply to a request with cookies may be read | Not sent: the widget uses no cookies |
| Access-Control-Expose-Headers | Which extra response headers page code may read | X-Shade-Version |
| Vary: Origin | Tells caches the reply differs by Origin | Sent with every reply |
The Vary row matters more than it looks. A cache, such as a CDN (a network of servers that keeps copies of replies near users), may store one reply and serve it to many callers.
Without Vary: Origin, a cache could hand one dealer a reply naming another dealer's origin, and the browser would block it. The Fetch Standard describes a related trap: a cached reply without Allow-Origin reused for a CORS request.
Expose-Headers is the quiet one. The Fetch Standard lets page code read only a few response headers by default: Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified and Pragma. In our test, a custom header read as null until the server listed it.
Here is a small, runnable version of the colour API's save endpoint with its CORS handling, using only Python's standard library. We ran it on Python 3.13 and called it from a page in Chromium 141. If you do not write Python yet, skip to the check that follows.
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
# Exact origins, as the browser sends them: scheme, host, port, no trailing slash
ALLOWED_ORIGINS = {"http://localhost:8311", "https://shop.example.in"}
WIDGET_KEYS = {"demo-key-1"}
def cors_headers(origin, preflight):
headers = {"Vary": "Origin"} # caches keep one copy per origin
if origin not in ALLOWED_ORIGINS:
return headers # no permission: the browser blocks the read
headers["Access-Control-Allow-Origin"] = origin # one exact origin, never *
headers["Access-Control-Expose-Headers"] = "X-Shade-Version"
if preflight:
headers["Access-Control-Allow-Methods"] = "GET, POST"
headers["Access-Control-Allow-Headers"] = "Content-Type, X-Widget-Key"
headers["Access-Control-Max-Age"] = "7200" # seconds; browsers cap it
return headers
class ColourAPI(BaseHTTPRequestHandler):
def reply(self, status, body=b"", preflight=False):
self.send_response(status)
for name, value in cors_headers(self.headers.get("Origin"), preflight).items():
self.send_header(name, value)
self.send_header("X-Shade-Version", "2026.10")
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_OPTIONS(self): # the preflight: answer it before any key check
self.reply(204, preflight=True)
def do_POST(self):
if self.headers.get("X-Widget-Key") not in WIDGET_KEYS:
return self.reply(401, b'{"error": "unknown widget key"}') # errors get CORS headers too
size = int(self.headers.get("Content-Length", 0))
try:
shades = json.loads(self.rfile.read(size))
except ValueError: # bad JSON still gets a readable reply
return self.reply(400, b'{"error": "body is not valid JSON"}')
self.reply(200, json.dumps({"saved": len(shades)}).encode())
ThreadingHTTPServer(("127.0.0.1", 8400), ColourAPI).serve_forever()The membership test is an exact string match against a set, so a look-alike domain cannot sneak through. Browsers send the origin with no trailing slash, so store it that way; in our test, an Allow-Origin with a trailing slash was blocked.
Notice the order inside the handler. OPTIONS is answered before any key check, and the 401 and 400 error replies carry CORS headers too. In our test, page code could read a 401 status only when the reply had CORS headers; otherwise it got Failed to fetch.
How do you check that the CORS headers are right?
Run the server, then send the same preflight a browser would, with curl. Curl does not enforce CORS, but it shows exactly which headers your server sends.
curl -i -X OPTIONS http://localhost:8400/shades \
-H "Origin: http://localhost:8311" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type,x-widget-key"Expect HTTP/1.0 204 No Content, then Access-Control-Allow-Origin: http://localhost:8311 and Access-Control-Max-Age: 7200 among the headers. Change the Origin to https://evil.example and run it again: the Allow-Origin line disappears, and only Vary: Origin remains of the CORS headers.
Now the real browser check. Serve any page on port 8311, for example with python3 -m http.server 8311 in an empty folder. Open http://localhost:8311, not 127.0.0.1, which is a different origin, and paste this into the developer console.
fetch('http://localhost:8400/shades', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-Widget-Key': 'demo-key-1'},
body: JSON.stringify(['Mint', 'Saffron'])
}).then(async r => console.log(r.status, await r.text(), r.headers.get('X-Shade-Version')))Expect 200 {"saved": 2} 2026.10 in the console. The server's terminal logs two requests: OPTIONS with status 204, then POST with status 200. A wrong key prints 401 and the error body, which the page can read because the reply carries CORS headers.
After the paint company shipped the same change, the palettes came back on all 340 dealer sites.
How do cookies and credentials change CORS?
Credentialed requests carry cookies or other login data, so CORS gets stricter. The server must name the exact origin and send Access-Control-Allow-Credentials: true; a wildcard is not allowed.
By default, a cross-origin fetch sends no cookies. The page must ask for them by setting the credentials option to include. The Fetch Standard defines credentials as HTTP cookies, TLS client certificates (certificates a browser can present to prove who the user is) and HTTP authentication entries.
Later, the team wants signed-in shoppers to see their saved palettes on any dealer site. That needs the shopper's login cookie. The first try sends Allow-Origin: * with Allow-Credentials: true, which the Fetch Standard forbids; Chromium 141 blocked it in our test.
The gymkhana explains why. A member may sign in a named guest for the members-only lounge, but no member may sign in anyone at all who turns up. The lounge carries the member's privileges, so the club insists on a name.
The first four rules come from the Fetch Standard, and each held in our Chromium 141 tests:
- Allow-Origin must name the exact origin. A * reply to a request with cookies was blocked.
- Allow-Credentials must be exactly true. The value True, with a capital T, was blocked.
- In Allow-Headers and Allow-Methods, * stops being a wildcard and means a header or method literally named *. A credentialed PUT failed under Allow-Methods: *.
- The preflight never carries the cookie. Our server saw the OPTIONS request without it and the PUT that followed with it.
- Third-party cookie rules still apply. MDN notes they may stop the cookie being sent at all, even when the server allows credentials.
That last point changed the design. The team dropped cookies for the widget. The shopper signs in through a small popup instead, and the widget sends a short-lived token in a header, only when its code chooses to.
How do you fix a CORS error the right way?
You fix a CORS error on the server that owns the data, by allowing the exact origins that should read it. Changes in the page's code cannot grant permission the server never gave.
Read the exact error
Open the developer console and network tab. Note whether the preflight failed, a header is missing, or the origin did not match.
Find the real calling origin
Check the Origin header the browser sent. Look for www, http versus https, and port differences.
Add it to the server allowlist
Store exact origins in config or a table. Echo the matching one back, and send Vary: Origin.
Handle OPTIONS early
Answer preflights with a 2xx status and the allow headers, before login or key checks run.
Put CORS headers on errors
Add the same headers to 4xx and 5xx replies, so the page can read and show the real problem.
Check with curl, then a browser
Curl shows which headers you send. Only a real page, from each kind of origin your users have, proves the browser accepts them.
A few fixes are popular online and wrong, or work only on your own laptop:
- Browser extensions that disable CORS. They change only your browser, so real users still see the error.
- Setting the fetch mode to no-cors. The request goes out, but the page gets an opaque reply: status 0 and an empty body in our test. It also silently drops a non-safelisted Content-Type: our application/json POST arrived as text/plain, so the API may reject it.
- Reflecting whatever Origin arrives back into Allow-Origin. That lets every website read your replies, including private ones if you also allow credentials.
Sometimes the cleaner fix is to avoid cross-origin calls entirely. If a page and its API can share one origin through a reverse proxy (a server that forwards requests to another server), CORS never comes up. A widget on 340 dealer domains cannot do that, so an allowlist is the right tool.
Beginners can stop here: you know what an origin is, why the browser blocks a read, what a preflight is and how to fix the server. The next section is for engineers who already ship CORS and want the details that cause hard-to-find bugs.
Which CORS details catch out experienced engineers?
Four details surprise people who already know the basics. The preflight cache is per URL, safelisted headers have size limits, the wildcard skips Authorization on paper only, and errors need CORS headers. We tested each on 19 October 2026 in Chromium 141, a 2025 build; current Chrome is newer, so recheck browser-specific results before relying on them.
The preflight cache is kept per URL, not per origin
The Fetch Standard keys each cached preflight by the calling origin, the exact URL, the credentials setting and a network partition key based on the top-level site. Max-Age therefore saves round trips only for repeat calls to the same URL.
In our test with Max-Age 600, five PUT requests to /pfc/a, /pfc/b, /pfc/a, /pfc/b and /pfc/a?page=2 sent three preflights. A changed query string counted as a new URL.
For a REST API (one that gives each resource its own URL) with IDs in the path, every new resource costs a fresh preflight for a non-simple call. Keep hot reads simple so they need no preflight, or serve the API from the page's origin.
Safelisted headers stop being safe when they are long
The Fetch Standard says a safelisted header loses that status if its value is longer than 128 bytes. It also caps the safelisted headers' combined value size at 1,024 bytes. In our test, an Accept header of 129 bytes triggered a preflight, and a short one did not.
The Authorization header and the wildcard disagree
The Fetch Standard says Access-Control-Allow-Headers: * never covers the Authorization header, so it must be listed by name. Browsers have not all followed it yet.
As of 19 October 2026, MDN's browser compatibility data (version 8.1.6) marks no browser as doing this by default. Firefox 115 and later can do it through a setting. Chromium 141 accepted * for Authorization in our test. List Authorization by name anyway, because browsers may start enforcing the standard.
| Case | What the server sent | Result |
|---|---|---|
| Text/plain POST | No CORS headers | POST reached the server and got a reply; page got Failed to fetch |
| POST with Content-Type: application/json | OPTIONS answered with 405 and no headers | Only OPTIONS reached the server |
| Custom header, no cookies | Allow-Headers: * | Allowed |
| Authorization header, no cookies | Allow-Headers: * | Allowed, though the standard says it should not be |
| PUT with cookies | Allow-Methods: * | Blocked: * is literal with credentials |
| Preflighted POST | Allow-Methods: PUT only | Allowed: GET, HEAD and POST always pass the method check |
| GET with cookies | Allow-Credentials: True | Blocked: the value must be lower-case true |
| Accept header, 129 bytes | Allow-Headers: Accept, Content-Type | Preflight sent first, then allowed |
| 401 error reply | Allow-Origin: * on the error | Page read status 401 |
| 401 error reply | No CORS headers on the error | Page got Failed to fetch |
The last two rows are why step 5 of the fix matters. Without CORS headers on errors, the widget cannot tell a bad key from a network failure, so it cannot show the dealer a useful message.
Is CORS a security feature that protects your API?
No, CORS protects browser users, not your API. It decides which pages may read replies inside a browser, but anyone can still call your API directly with curl or a script.
Page code cannot change the Origin header: in our test, a fetch that set its own Origin was ignored and the real one was sent. Outside a browser, anyone can send any Origin. Our curl POST with Origin https://evil.example and a valid key got 200.
So the colour API still needs real checks: the widget key, rate limits (caps on requests per caller) and login for private data. OWASP, a non-profit web security project, says in its HTML5 security cheat sheet not to rely only on the Origin header for access control.
Here is where the gymkhana picture breaks. The club's wall stops strangers everywhere. The API has no wall at all: anyone with a terminal can reach it. CORS is only the gate guard inside browsers, protecting the shopper's own logged-in sessions.
CORS also does not stop cross-site request forgery, or CSRF: a trick where another site makes your browser send a request carrying your cookies. OWASP's cheat sheet says CORS does not stop data going to an unauthorised place, and servers still need normal CSRF protection.
Remember the blocked POST that still reached the server. A form-style request skips the preflight, so if it changes data, the change happens even though the attacker's page cannot read the reply. JSON sent without the application/json label skips it too: in our test it went out as text/plain. So a JSON API should reject requests whose Content-Type is not application/json.
How do you explain CORS in a system design interview?
Explain it as the browser's permission check for cross-origin reads, then show how your API answers it safely and cheaply.
Define origin and the policy
Scheme, host and port make an origin. The same-origin policy stops pages reading other origins' replies.
Name CORS as the opt-in
The server sends Access-Control-Allow-Origin. The browser checks it before handing the reply to the page.
Explain the preflight
Content-Type: application/json, custom headers and methods like PUT or DELETE trigger an OPTIONS check first. Max-Age caches the answer, per URL.
Cover credentials
Cookies need an exact origin and Allow-Credentials true. Mention third-party cookie limits for embedded widgets.
Place it in the design
Handle CORS once, before auth, at the API gateway (the single front door for API calls) or in shared middleware. Keep the allowlist in config, and send Vary: Origin.
State what it does not do
It does not stop direct calls or CSRF. You still need auth, rate limits and CSRF tokens.
Questions people ask
- What does CORS stand for?
- CORS stands for Cross-Origin Resource Sharing. It is a set of HTTP headers a server sends to tell the browser which other origins may read its responses. The browser then enforces that choice for every page.
- Is a CORS error a frontend or a backend problem?
- It shows up in the frontend, but it is almost always fixed in the backend. The browser blocks the read because the server's reply lacks the right CORS headers. Only the server that owns the data can grant permission to another origin.
- Is it safe to set Access-Control-Allow-Origin to *?
- It is safe only for data you would publish on the open internet anyway, such as a public list of paint shades. Data that needs no login but sits on an internal network is not public: with *, the server gives every website permission to read it through an employee's browser. Browsers refuse a wildcard on requests that carry cookies. For anything private, list exact origins instead.
- Why is my browser sending an OPTIONS request I never wrote?
- That is a CORS preflight, sent automatically by the browser. It happens when a cross-origin request sets Content-Type: application/json, adds a header outside the safelist, or uses a method other than GET, HEAD or POST. Your server must answer it with a 2xx status and the allow headers.
- Does CORS apply to mobile apps or server-to-server calls?
- Not for native code, but yes for app screens built as web pages. CORS is enforced by web browsers, to protect the person using them. Native mobile app code, curl and backend services that make HTTP calls directly ignore CORS headers. Hybrid apps show their screens inside a WebView, a browser engine embedded in the app, and the WebView applies CORS.
- How long can a browser cache a CORS preflight?
- The server sets this with Access-Control-Max-Age, in seconds; without it, the Fetch Standard uses 5 seconds. MDN says Firefox caps it at 24 hours and Chromium-based browsers at 2 hours. Each cached answer covers one URL only, so a new URL needs a new preflight.
- Does localhost need CORS during development?
- Yes, if your page and your API run on different ports. A page on port 3000 and an API on port 8000 are different origins. Either allow the development origin on the API, or use your dev server's proxy so both share one origin.
Sources and further reading
- MDN Web Docs: Cross-Origin Resource Sharing (CORS) guide
- WHATWG Fetch Standard: the HTTP CORS protocol, preflight cache and HTTP caches
- MDN Web Docs: Access-Control-Allow-Headers (wildcard and Authorization)
- MDN Web Docs: Access-Control-Max-Age (default and browser caps)
- MDN Web Docs: Origin header (when it is null)
- RFC 6454: The Web Origin Concept (Barth, December 2011)
- OWASP HTML5 Security Cheat Sheet: Cross Origin Resource Sharing