Skip to main content

Same-Origin Policy: React Security Guide

The same-origin policy is the browser's foundational security model: it restricts JavaScript running in one origin from accessing data in another origin without explicit permission. An origin is defined by the scheme, domain, and port—so https://example.com:443 and https://api.example.com:443 are two different origins, and the browser will block a fetch() from the first to the second unless the server sends CORS headers. Understanding SOP is the prerequisite for grasping CSRF, CORS, and CSP. When you deploy a React app, the browser enforces this policy automatically; your job is to work with it, not against it.

In 2026, the same-origin policy remains the single most important line of defense against data theft, session hijacking, and unauthorized API calls. If you skip SOP, the rest of the series—CSRF tokens, CORS rules, CSP directives—makes no sense.

What Is an Origin?

An origin consists of three components: the scheme, the domain, and the port. Two URLs share the same origin if and only if all three match exactly.

Scheme: https
Domain: example.com
Port: 443 (implicit for HTTPS)
Full origin: https://example.com
URLOriginSame as https://example.com?
https://example.com/pagehttps://example.comYes
https://example.com:8443/pagehttps://example.com:8443No (port differs)
http://example.com/pagehttp://example.comNo (scheme differs)
https://api.example.com/pagehttps://api.example.comNo (domain differs)
https://example.com.evil.com/pagehttps://example.com.evil.comNo (domain differs)

The table above shows how strict the browser is: a single character difference means a different origin, triggering cross-origin restrictions.

Why the Browser Enforces SOP

Imagine you are logged into your bank at https://mybank.com. In another tab, you visit a malicious site at https://evil.com. Without the same-origin policy, JavaScript code on evil.com could:

  1. Call fetch('https://mybank.com/api/balance') and read your account balance.
  2. Call fetch('https://mybank.com/api/transfer', { method: 'POST', body: { amount: 5000, to: 'attacker' } }) and drain your account.
  3. Access document.cookie to steal your session token.

The browser prevents this by blocking all three actions. JavaScript in evil.com is confined to the evil.com origin; it cannot read responses, cookies, or local storage from mybank.com. This is the security model React inherits. Your React component running in the browser cannot make an unauthenticated request to a different origin without the server's consent.

How the Browser Enforces SOP: Read vs. Write

The same-origin policy is asymmetric. The browser blocks:

  • Read access: JavaScript cannot inspect the response body, status code, or headers of a cross-origin request. For example, fetch('https://api.example.com/data') will fail with a CORS error if api.example.com does not send Access-Control-Allow-Origin.
  • Write access (for simple requests): JavaScript can make certain cross-origin requests (POST, GET, HEAD) if they use "simple" headers (no Authorization, no Content-Type: application/json), but the response is still unreadable.

The browser does not block:

  • Form submissions: form.submit() to a cross-origin target is allowed (no CORS check).
  • Script tags: script src="https://api.example.com/data.js" is allowed.
  • Image tags: img src="https://api.example.com/image.jpg" is allowed.
  • Style sheets: link rel="stylesheet" href="https://api.example.com/style.css" is allowed.

This distinction is crucial for understanding CSRF and CORS later in the series.

Same-Origin Policy in React

React components use fetch() by default for API calls. Here is what SOP means in practice:

// Example: React component making an API call
import React, { useEffect, useState } from 'react';

export default function Dashboard() {
const [data, setData] = useState(null);

useEffect(() => {
// If this React app runs at https://app.example.com
// and you fetch from https://api.example.com,
// the browser will block the read unless the server
// sends CORS headers (see later articles).
fetch('https://api.example.com/user')
.then(res => res.json())
.then(data => setData(data))
.catch(err => console.error('CORS blocked:', err));
}, []);

return <div>{data ? data.name : 'Loading...'}</div>;
}

When the browser encounters fetch('https://api.example.com/user') from code running in https://app.example.com, it:

  1. Sends the HTTP GET request to the server (write is allowed).
  2. Receives the response from the server.
  3. Checks the response headers for Access-Control-Allow-Origin: https://app.example.com (or *).
  4. If the header is missing, JavaScript code cannot read the response body. The promise rejects with a NetworkError.

This is SOP in action: the request was sent, but the response was hidden.

Subdomains and the document.domain Trick

Historically, developers could set document.domain = 'example.com' to relax SOP between subdomains. However, this is deprecated and removed in modern browsers. In 2026, rely on CORS instead.

// DEPRECATED - do not use
document.domain = 'example.com'; // This will not work in modern browsers

If you need to share data between https://app.example.com and https://api.example.com, use CORS with Access-Control-Allow-Origin: https://app.example.com or Access-Control-Allow-Origin: https://*.example.com (not supported; list each subdomain explicitly).

Key Takeaways

  • An origin is the combination of scheme, domain, and port. The browser treats them as atomic: change any one component and you have a different origin.
  • The same-origin policy restricts JavaScript from reading cross-origin responses unless the server explicitly allows it via CORS headers.
  • React components using fetch() will hit SOP restrictions immediately if the server does not send the correct headers.
  • SOP allows form submissions, script tags, and images to load from any origin (which is why CSRF is possible; see the next article).
  • Subdomains are treated as separate origins; there is no way to relax SOP locally. Use CORS on the server.

Frequently Asked Questions

Can I disable the same-origin policy in a production React app?

No. SOP is enforced by the browser and cannot be disabled by JavaScript. You must configure your backend API to send CORS headers. If you use a proxy during local development, remember to remove it before deploying.

Why does fetch() fail with "No 'Access-Control-Allow-Origin' header"?

Your React app is running in one origin (e.g., https://app.example.com) and your API is in another (e.g., https://api.example.com). The browser sent the request but blocked JavaScript from reading the response because the server did not send Access-Control-Allow-Origin. The server must explicitly consent to cross-origin requests. See articles 4–5 for CORS setup.

Is localhost the same origin as 127.0.0.1?

Yes, both refer to the local machine. However, http://localhost:3000 and http://localhost:3001 are different origins (different ports). A React app on port 3000 cannot read API responses from port 3001 without CORS headers.

Does the same-origin policy protect against CSRF attacks?

Partially. SOP prevents a malicious site from reading your bank response, but it does not prevent evil.com from triggering a form submission to your bank (because form submissions bypass SOP). CSRF tokens (article 2) are the additional defense. SOP and CSRF tokens are complementary.

Further Reading