CORS in depth: preflights, credentials and fixing the errors
How CORS lets a server allow cross-origin reads, when the browser sends an OPTIONS preflight, every Access-Control header, why * cannot be used with cookies, why curl ignores CORS, and how to fix common errors.
Comes up in: Web fundamentals, Recruiter screen, Debugging
Learn#
Ayesha’s React app at app.example calls her API at api.example. It works in Postman and curl, but the browser console says “blocked by CORS policy”. The API is not broken; the browser is protecting users.
CORS (cross-origin resource sharing) is how a server tells the browser which other origins may read its responses. The same-origin policy blocks those reads by default (WEB.14.01); CORS headers make exceptions. Postman and curl are not browsers, so they ignore CORS entirely.
A “simple” request (GET, HEAD or POST with only basic headers and form-like content types) is sent directly, and the response’s Access-Control-Allow-Origin decides whether the page may read it.
Anything else, such as PUT, DELETE, JSON bodies or an Authorization header, first triggers a preflight request: an OPTIONS request asking permission. Only if the answer allows it does the browser send the real request.
Network
empty
app.example sends fetch(api.example/orders, { method: 'PUT', headers: { 'Content-Type': 'application/json' } }). PUT and JSON are not "simple".
Read the steps as text
- app.example sends fetch(api.example/orders, { method: 'PUT', headers: { 'Content-Type': 'application/json' } }). PUT and JSON are not "simple".
- So the browser first sends a preflight: OPTIONS with Origin, Access-Control-Request-Method: PUT and -Headers: content-type.
- The API answers 204 with Allow-Origin: https://app.example, Allow-Methods: PUT, Allow-Headers: content-type, Max-Age: 600.
- Now the real PUT is sent. Its response also carries Allow-Origin, so the page may read it. The preflight is cached for 10 minutes.
Predict#
Predict first
Your API returns Access-Control-Allow-Origin: * and the app calls fetch(url, { credentials: 'include' }). What happens?
With credentials, the server must name the exact origin and send Access-Control-Allow-Credentials: true. A wildcard with cookies would let any site read a logged-in user’s data. People assume * is the most permissive setting, so it should work.
Interview angle#
Say it like this — about 30 seconds
“CORS lets a server opt in to cross-origin reads. Simple requests, meaning GET, HEAD or POST with basic headers and form content types, are sent directly, and the browser checks Access-Control-Allow-Origin on the response. Anything else, such as PUT, a JSON body or an Authorization header, triggers an OPTIONS preflight first. With credentials, the server must echo the exact origin and send Allow-Credentials true; a wildcard is not allowed. Postman and curl ignore CORS because only browsers enforce it.”
Follow-up What is CORS, and what triggers a preflight request?
Follow-up Why does my request work in Postman or curl but fail in the browser?
Follow-up Why can't you use Access-Control-Allow-Origin: * with cookies?
Practice#
On your own
Write the browser’s two CORS decisions: does this request need a preflight, and may the page read the response?
Done when
- Simple methods, safelisted headers and form-like content types skip the preflight; anything else needs one.
- Without credentials, the exact origin or
*allows reading. - With credentials, only the exact origin plus
allow-credentials: trueallows reading.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
Who enforces CORS?
The browser. curl and Postman ignore it.
Name three things that trigger a preflight.
A PUT or DELETE method, a JSON content type, an Authorization header.
What does a credentialed CORS response need?
The exact origin in Allow-Origin, and Allow-Credentials: true.
What does Access-Control-Max-Age do?
Lets the browser cache the preflight result for that many seconds.
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
Allow-Origin (the origin or *), Allow-Credentials, Allow-Methods and Allow-Headers (for preflights), Expose-Headers (headers JavaScript may read), and Max-Age (how long to cache the preflight) Source: MDN: Cross-Origin Resource Sharing (CORS).Check yourself: Which header lets JavaScript read a custom response header?
Access-Control-Expose-Headers.
How it works underneathSenior rounds
omit never, same-origin (default) only to your own origin, include cross-origin too, which then requires the stricter CORS rules.Vary: Origin when you echo the origin, so caches do not serve one origin’s answer to another.Check yourself: What is fetch's default credentials mode?
same-origin.
Edge cases and trapsWhere people slip
Allow-Origin, a preflight that returned 401 or 404 (OPTIONS must succeed without auth), * with credentials, or a redirect during preflight.Check yourself: Why does a CORS error hide the real status code?
The browser withholds the whole response, so JavaScript sees only a network error.
In productionStaff-level answers
server.proxy) sends /api to the backend, so the browser sees one origin. In production, either serve the API on the same origin or allow an exact list of origins on the server Source: Fetch Standard: CORS protocol.Check yourself: Why use a dev-server proxy?
The browser then talks to one origin, so CORS is not needed in development.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-11-01. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- What is CORS and what triggers a preflight request?
- Why does my request work in Postman/curl but fail in the browser?
- Why can't you use
Access-Control-Allow-Origin: *with cookies?