Same-Origin Policy (SOP)

Fundamental web security concept that prevents scripts from one origin from interacting with resources from another origin.

What is Same-Origin Policy (SOP)?

Same-Origin Policy (SOP) is a fundamental security mechanism implemented by web browsers that prevents scripts or documents from one origin from interacting with resources from another origin. SOP is the cornerstone of web security, designed to protect users from malicious websites that might attempt to access sensitive data from other sites.

The policy works by restricting how documents or scripts loaded from one origin can interact with resources from another origin. This prevents a wide range of security vulnerabilities, including Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF) attacks.

How Origins Are Defined

An origin is defined by the combination of three components:

  1. Protocol (Scheme): http:// or https://
  2. Hostname (Domain): example.com
  3. Port: :80, :443, or other port numbers

Examples of same vs. different origins:

URL 1URL 2Same Origin?Reason
https://example.comhttps://example.com✅ YesIdentical protocol, domain, and port
https://example.comhttp://example.com❌ NoDifferent protocol
https://example.comhttps://sub.example.com❌ NoDifferent subdomain
https://example.com:443https://example.com:8080❌ NoDifferent port
https://example.comhttps://example.org❌ NoDifferent domain

What SOP Restricts

The Same-Origin Policy restricts several types of cross-origin interactions:

  1. DOM Access: Scripts cannot access the DOM of documents from different origins
  2. Cookies: Cookies are only sent to the origin that set them
  3. AJAX Requests: XMLHttpRequest and Fetch API calls to different origins are blocked
  4. Storage: localStorage and sessionStorage are isolated by origin
  5. Web Workers: Workers cannot access resources from different origins

Exceptions to SOP

While SOP is strict, there are several exceptions and mechanisms that allow controlled cross-origin interactions:

  1. Cross-Origin Resource Sharing (CORS): Allows servers to specify which origins can access their resources
  2. JSONP (JSON with Padding): Legacy technique for cross-origin data fetching
  3. Cross-Document Messaging: Secure communication between documents from different origins
  4. WebSockets: Not subject to SOP when established
  5. Images, Scripts, and Stylesheets: Can be embedded from different origins

Security Benefits

Prevents data theft:

  • Stops malicious websites from reading sensitive data from other sites
  • Protects user sessions and authentication tokens
  • Prevents unauthorized access to private information

Mitigates attacks:

  • Blocks XSS attacks from stealing data
  • Prevents CSRF attacks
  • Stops session hijacking attempts

Enforces isolation:

  • Keeps different websites separate
  • Prevents interference between web applications
  • Maintains user privacy across different services

Common SOP Scenarios

Blocked by SOP:

// Trying to access content from a different origin
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data)); // Blocked by SOP

Allowed by SOP:

// Accessing resources from the same origin
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data)); // Allowed

Cross-origin embedding (allowed):

<!-- These are allowed by SOP -->
<img src="https://other-site.com/image.jpg" alt="Cross-origin image">
<script src="https://cdn.example.com/library.js"></script>
<link rel="stylesheet" href="https://cdn.example.com/styles.css">

Bypassing SOP (Legitimate Methods)

1. Cross-Origin Resource Sharing (CORS)

Access-Control-Allow-Origin: https://trusted-site.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Credentials: true

2. JSONP (Legacy Method)

// Client-side
function handleResponse(data) {
  console.log(data);
}

const script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);

3. Cross-Document Messaging

// Sender
targetWindow.postMessage({ message: 'Hello' }, 'https://target-origin.com');

// Receiver
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://sender-origin.com') return;
  console.log(event.data);
});

4. WebSockets

// WebSockets are not subject to SOP
const socket = new WebSocket('wss://api.example.com');
socket.onmessage = (event) => {
  console.log(event.data);
};

Security Risks and Considerations

Overly permissive CORS:

  • Using Access-Control-Allow-Origin: * can expose sensitive data
  • Allowing credentials with wildcard origins is dangerous

JSONP vulnerabilities:

  • No built-in error handling
  • Vulnerable to XSS if callback parameter is not sanitized
  • No support for modern security features

Cross-document messaging risks:

  • Must validate the origin of incoming messages
  • Without origin validation, can be exploited by malicious sites

Subdomain vulnerabilities:

  • Different subdomains are considered different origins
  • Requires explicit CORS configuration for cross-subdomain access

Best Practices

  1. Validate all cross-origin interactions - never trust external origins
  2. Use CORS judiciously - be specific with allowed origins
  3. Avoid JSONP - use CORS instead for modern applications
  4. Validate message origins in cross-document messaging
  5. Use secure cookies with SameSite attributes
  6. Implement proper authentication for sensitive operations
  7. Combine with other security headers:

Example: Secure Cross-Origin Communication

Server-side (Node.js with Express):

app.use((req, res, next) => {
  // Only allow specific trusted origins
  const allowedOrigins = ['https://trusted-site.com', 'https://partner-site.com'];
  const origin = req.headers.origin;

  if (allowedOrigins.includes(origin)) {
    res.header('Access-Control-Allow-Origin', origin);
    res.header('Access-Control-Allow-Credentials', 'true');
  }

  next();
});

Client-side (Secure Cross-Document Messaging):

// Sender
const targetWindow = document.getElementById('iframe').contentWindow;
targetWindow.postMessage(
  { action: 'update', data: sensitiveData },
  'https://trusted-receiver.com'
);

// Receiver
window.addEventListener('message', (event) => {
  // Always validate the origin
  if (event.origin !== 'https://trusted-sender.com') {
    console.warn('Message from untrusted origin:', event.origin);
    return;
  }

  // Process the message
  console.log('Received:', event.data);
});