GuidesUNDERSTAND

The same-origin policy

The same-origin policy is a foundational browser security mechanism that restricts how documents or scripts loaded from one origin interact with resources from another origin. It isolates potentially malicious documents by preventing untrusted code from reading confidential data across origins. Web security models depend on this boundary to keep authenticated sessions and stored state private.

What an origin is

An origin is defined by the triple of scheme, host, and port. Two URLs share an origin only when all three components match exactly. When a URL omits an explicit port, the browser infers port 443 for https and port 80 for http.

Origin comparisons against https://app.example.com
URLSame origin?Why
https://app.example.com/docs/getting-startedYesOnly the path differs
https://app.example.com:443/apiYes443 is the default port for https, so the port matches
https://api.example.comNoDifferent host
http://app.example.comNoDifferent scheme
https://app.example.com:8443NoDifferent port

What the policy blocks

The browser enforces origin isolation by preventing unauthorized cross-origin reads and restricting DOM object access. Scripts cannot inspect response payloads or access client-side storage across distinct origins without explicit platform-level exemptions.

Interactions the same-origin policy restricts
InteractionWhat happens cross-origin
Reading fetch/XHR responsesBlocked unless CORS grants access via response headers.
DOM access to documents in other frames or windowsBlocked. Scripts cannot inspect or modify cross-origin DOM nodes, and only a small set of window properties such as postMessage stays reachable.
localStorage, sessionStorage, and IndexedDBPartitioned per origin. Browsers prevent all cross-origin reads and writes.

What the policy allows

The web relies on hyperlinking and external assets, so the browser permits cross-origin writes and resource embedding by default.

  • Cross-origin writes are allowed, including link clicks, server redirects, and HTML form submissions.
  • Cross-origin embedding is allowed for script tags, stylesheets, images, media elements, font faces, and iframes, though sites can refuse framing using the X-Frame-Options header.
  • Embedding leaks limited information, such as rendered image dimensions, whether an external resource loaded, and side effects from executed scripts, while syntax error details for cross-origin scripts stay hidden.

Why CSRF exists despite the policy

Cross-site request forgery exists because the policy restricts cross-origin reads rather than cross-origin writes. When a user visits an untrusted site that triggers a form submission targeting an authenticated service, the browser attaches stored session cookies automatically to that request, subject to modern SameSite cookie defaults such as Lax. The destination server receives the request and executes the corresponding action.

The policy prevents the calling page from reading the response, but it does not stop the write from completing on the server. Applications prevent this attack by requiring an unguessable CSRF token in state-changing forms and requests. Because cross-origin scripts cannot read responses or documents containing the token, untrusted sites cannot retrieve the value needed to forge state changes.

CORS is the opt-in relaxation

Cross-Origin Resource Sharing provides the standardized opt-in relaxation for read restrictions under the policy. A server returns an Access-Control-Allow-Origin header naming the requesting origin, or an asterisk for public data requested without credentials, and the browser releases the response body to JavaScript. When you control the target API, adding the Access-Control-Allow-Origin header to your own server is the correct fix. For third-party public APIs you cannot modify, a public proxy like cors.dev can re-serve public data with the needed headers for GET and HEAD requests to any public host. Because any proxy operator can observe traffic, route only public data through a proxy.

You can check whether two addresses share an origin in JavaScript by instantiating the URL object and comparing its origin property.

Compare origins in JavaScript
// Compare .origin, never the raw URLs: default ports normalize away
const page = new URL('https://app.example.com:443/docs');
page.origin; // 'https://app.example.com'

const api = new URL('https://api.example.com/users');
api.origin; // 'https://api.example.com'

api.origin === page.origin; // false: this call needs CORS permission

Good questions.

Does the same-origin policy block my request from being sent?

For safelisted requests, no: the browser dispatches the request, the server processes it, and the browser withholds the response body from script access unless CORS grants permission. For non-safelisted requests the browser first sends an OPTIONS preflight, and the real request goes out only when the server opts in. See the preflight explainer.

Are subdomains the same origin?

No, the host component must match exactly. For example, app.example.com and api.example.com are different hosts, so the browser treats them as distinct origins.

How do two cross-origin windows talk to each other?

Cross-origin windows and iframes communicate using window.postMessage. The historical document.domain origin relaxation property is deprecated in modern browsers.

Why do I get CORS errors opening local files?

Modern browsers treat pages loaded from file: URLs as opaque origins. Because opaque origins do not match sibling files, scripts encounter CORS errors when issuing fetch or XMLHttpRequest calls locally.