skip to content

Why does a React Native app's fetch call never fail with a CORS error, when the same request from a web page does?

level: middleimportance: must knowfreq 52%

answer

  1. who enforces CORS?
  2. no page origin in a native app
  3. native HTTP stack, not a browser
  4. no preflight OPTIONS request
  5. CORS is not API access control

basics

~20 s

CORS is a rule browsers enforce on pages from another origin. A React Native app's fetch uses the native HTTP stack, which has no page origin and no CORS check, so CORS headers neither block nor protect anything; the API must authenticate callers.

solid answer

~50 s

CORS is enforced by the browser on behalf of the user: it checks the response's `Access-Control-Allow-*` headers against the page's origin and sends preflight `OPTIONS` requests for non-simple calls. In React Native, `fetch` and `XMLHttpRequest` are thin JavaScript layers over the platform's native networking (`NSURLSession` on iOS, OkHttp on Android). There is no page origin to compare, no preflight, and nothing that reads the CORS headers, so a request that a browser would block simply succeeds. The React Native docs say it directly: there is no concept of CORS in native apps. The consequence matters more than the mechanism: restricting allowed origins does nothing against a mobile app, `curl` or a script, so the API must rely on authentication and authorisation. The exception is React Native Web, which runs in a browser and gets CORS back.

go deeper

for a junior

Recall that CORS is enforced by browsers and that React Native's fetch uses native HTTP clients, so CORS headers neither block nor help the app.

for a middle

Explain the mechanism: no page origin, no preflight, response headers ignored, and name the React Native Web exception where CORS returns.

for a senior

Draw the security consequence: CORS is not API protection, so authenticate every request, treat the app as a public client and keep real secrets on a backend.

for a principal

Discuss one API serving web and mobile clients: per-origin CORS policy for the browser, token authentication for everyone, and why client identity can never be trusted from the app alone.

## What CORS actually is **CORS (Cross-Origin Resource Sharing)** is a browser mechanism. Browsers apply the **same-origin policy** to scripts: a page loaded from `https://app.example` may not read responses from `https://api.other` unless that server opts in with headers such as `Access-Control-Allow-Origin`. For requests that are not "simple" (a JSON `Content-Type`, custom headers, methods other than GET/HEAD/POST), the browser first sends a **preflight** `OPTIONS` request and only proceeds if the answer allows it. The key point is **who enforces it**: the browser, protecting the user's cookies and session on other sites from a malicious page. The server merely declares a policy; the client decides whether to honour it. ## Why React Native has no CORS In React Native, JavaScript runs in Hermes on the device, and networking is delegated to native code: - **React Native's built-in `fetch`** is a polyfill layered on React Native's own `XMLHttpRequest`, which calls the native networking module. - **The native module** uses `NSURLSession` on iOS and OkHttp on Android, the same HTTP clients a fully native app uses. - **In Expo SDK 56 and later**, the global `fetch` is `expo/fetch`, which also calls a native module directly. None of these layers has a **page origin**, and none implements the CORS algorithm. The React Native networking docs state that the security model differs from the web because there is no concept of CORS in native apps. In practice: 1. No preflight `OPTIONS` request is sent before a JSON `POST` or a request with an `Authorization` header. 2. `Access-Control-Allow-*` response headers are ignored; a response is readable whatever they say. 3. The `mode` option of `fetch` (`'cors'`, `'no-cors'`) has nothing to enforce. ## What this means for the API | Belief | Reality for native clients | |---|---| | "Our CORS allow-list stops other apps using our API" | CORS is enforced by browsers only; the app, `curl` and scripts ignore it | | "The mobile app needs the API's CORS headers" | It works with or without them | | "A wrong CORS config will break the app" | It breaks only the web client | | "No CORS means the app is less secure" | CORS never protected the API; authentication does | So the design consequences are: - **Authenticate every request** on the server, typically with a bearer token, and authorise per resource. - **Treat the app as a public client**: anything shipped in the bundle, including API keys, can be extracted, so a key in the app is an identifier, not a secret. - **Rate-limit and validate** server-side exactly as you would for any internet client. ## Seeing it for yourself A quick experiment makes the point concrete. Point both a small web page and a React Native screen at an endpoint that returns JSON **without** any `Access-Control-Allow-Origin` header, and send a `POST` with `Content-Type: application/json`: - In the browser, the console shows a CORS error, the server log shows an `OPTIONS` preflight (or a response the page is not allowed to read), and your `.then` never gets the data. - In the React Native app, the server log shows only the `POST`, and the app renders the JSON. The server behaved identically both times. Only the client changed, which is the whole lesson: **CORS is a policy the browser applies to itself**, not a gate on the server. ## Where CORS comes back - **React Native Web / Expo web builds** run in a real browser, so the same `fetch` code is subject to CORS there. A codebase that targets web too needs correct CORS headers for the web origin, and a bug can appear only on the web build. - **WebView content** is a web page inside the app; its scripts follow the browser rules of the embedded engine, not React Native's networking. Debugging tools do not change this. React Native DevTools inspects the app over the Chrome DevTools Protocol, but your JavaScript still runs on the device and its requests still go through the native stack. ## The interview answer Say **who** enforces CORS (the browser), **why** React Native skips it (native HTTP clients, no origin, no preflight) and **what follows** (CORS is not access control; secure the API with authentication). Adding the React Native Web exception shows you have shipped a multi-platform codebase.

  • Does the absence of CORS mean a React Native app can safely embed a third-party API secret and call that API directly?
    No. Anything in the JavaScript bundle or the binary can be extracted, so a secret shipped in the app is effectively public. Keep privileged keys on your own backend and have the app call that backend with a per-user token instead.
  • Your team adds an Expo web target and the same fetch code starts failing. What changed?
    The web build runs in a browser, which enforces CORS. Requests from the web origin now need the API's `Access-Control-Allow-Origin` and, for JSON or authorised calls, a successful preflight. The native builds were never subject to that check.

saying these in an interview costs you the question

  • The app needs Access-Control-Allow-Origin set for its bundle identifier.
  • A strict CORS allow-list stops unofficial clients from calling our API.
  • React Native sends a preflight OPTIONS request before JSON POSTs.
  • Hermes turns the CORS check off only in release builds.
  • React Native Web builds are also exempt from CORS.