Independent comparison · no paid rankings
Home / Blog / Technical / CORS: fix browser access without opening the API to the world

CORS: fix browser access without opening the API to the world

CORS console error: not the API lock but browser policy — origin whitelist, OPTIONS preflight, and independent auth.

Hébergeurs.eu Editorial Team 4 min read

The support ticket arrives with a screenshot: « CORS blocks our API ». The frontend developer insists the backend « open CORS to * ». Meanwhile curl without an Origin header works fine. The confusion costs days because CORS is treated as a server lock — when it is a browser policy controlling who may read the JavaScript response.

CORS does not protect your API from direct attacks: curl, Postman, server scripts, and native mobile apps ignore CORS. Authentication (JWT, session, mTLS) remains mandatory. CORS answers one question: may https://app.example.com expose the JSON response to its JavaScript?

Recurring incidents come from preflight OPTIONS returning 401, * incompatible with cookies, or duplicate CORS headers between nginx and the framework. Here is how to structure a clear policy without opening the door to the entire web.

Preflight OPTIONS and methods

« Non-simple » requests (JSON POST, custom headers, PUT/DELETE) trigger an OPTIONS preflight. The browser sends Origin, Access-Control-Request-Method, sometimes Access-Control-Request-Headers. The server must respond 204/200 with CORS headers before the real request.

Classic mistake: auth middleware treats OPTIONS like POST and returns 401. The browser shows an opaque CORS error — developers blame CORS, the cause is auth.

Exclude OPTIONS from auth on public API routes. Test with:


curl -X OPTIONS -H "Origin: https://app.example.com" \

  -H "Access-Control-Request-Method: POST" \

  -i https://api.example.com/v1/resource

Origins, credentials, and Vary

ScenarioAccess-Control-Allow-OriginCredentials
Public API without cookieexact origin or listfalse
SPA with session cookiewhitelisted exact origintrue + Allow-Credentials
* with cookiesforbidden by specbrowser failure

With Access-Control-Allow-Credentials: true, return the exact request origin if whitelisted — never *.

Add Vary: Origin on dynamic CORS responses so CDNs do not cache the wrong origin.

One layer emits headers

nginx and Express (or Laravel, Django) both adding Access-Control-Allow-Origin produce duplicate headers — some browsers fail intermittently, the worst production bug class.

Choose: CORS at the edge (nginx/API gateway) or in the application — not both. Document in architecture ADR.

Annual audit: grep nginx config + middleware code — single source of truth.

CORS ≠ API security

An attacker forges a request from their server without a browser Origin — CORS does not apply. Rate limiting, auth, input validation, WAF remain necessary.

Short internal doc: « Native mobile apps do not need CORS » — reduces false backend tickets.

OpenAPI: list allowed origins per environment and copy-paste preflight curl example for partners.

Testing and observability

Partner Postman collection maintained in CI — CORS regression caught pre-merge.

Metrics: OPTIONS 204 vs 401 ratio, unknown origins rejected (aggregated log without PII).

On incident: reproduce with curl preflight first, then browser with cache disabled — avoids false Chrome extension leads.

API gateway review

Annual audit: only one layer emits CORS headers. Duplicate headers from nginx plus app middleware break browsers intermittently — worst production bug class.

Preflight OPTIONS must return quickly — timeout here looks like CORS failure to developer but is server slowness.

Document that mobile native apps do not need CORS — reduces false security tickets to backend team.

Operational follow-up

CORS documented in OpenAPI cuts partner support tickets — worthwhile investment. Copy-paste preflight curl example per environment. Partner Postman collection in CI — CORS regression caught pre-merge. Document gaps between host marketing and field measurement in the quarterly review.

Quarterly follow-up

CORS documented in OpenAPI cuts partner support tickets — worthwhile investment. Copy-paste preflight curl example per environment. Partner Postman collection in CI — CORS regression caught pre-merge. Document gaps between host marketing and field measurement in the quarterly review.

Decide and move forward without blind spots

  1. Reproduce preflight curl — OPTIONS with Origin and Access-Control-Request-Method before touching nginx.
  2. Explicit origin whitelist — never arbitrary reflect; Vary: Origin if CDN.
  3. Skip auth on OPTIONS — dedicated middleware; 401 on preflight = classic ticket.
  4. Partner integration kit — exact Origin, credentials, fetch example, 401 vs browser CORS.
  5. Independent API auth — JWT, rate limit, CSRF if cookies; CORS is not the server lock.

Cross-domain API: comparison tool, directory, backend blog.

Frequently asked questions

Does CORS protect the API from attacks?

No: it tells the browser which origins may read the response. curl, native mobile, and server scripts ignore CORS — API authentication remains mandatory.

Why does * fail with cookies?

The spec forbids Access-Control-Allow-Origin: * when Access-Control-Allow-Credentials: true. You must return the exact request origin from a whitelist.

What if OPTIONS returns 401?

Auth middleware blocks preflight. Exclude OPTIONS from auth — a frequent cause of « broken CORS » tickets.

How to test without the browser?

Reproduce with curl -X OPTIONS -H Origin:https://app.example.com -H 'Access-Control-Request-Method: POST' on the API URL.


Document allowed origins in partner onboarding — fewer Friday night emergency deploys.

Compare European hosts

Filter by compliance, location and use case — then open the sheets to verify the real scope.

Browse the directory
Blog

Related reading

All articles →