Cross Origin

High Level

Details

What It Is

Browser security mechanism that controls which resources a webpage can load. Prevents code injection attacks (XSS, clickjacking) by restricting sources of scripts, styles, images, and other content.

How It Works

Server sends CSP headers or webpage includes CSP meta tags. Browser enforces the policy by blocking unauthorized resource loads and reporting violations.

<!-- Via meta tag -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; script-src 'self' https://trusted.com">

<!-- Via HTTP header (preferred) -->
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.com

Key Directives

Content Types

  • default-src - Fallback for all other directives
  • script-src - JavaScript sources
  • style-src - CSS sources
  • img-src - Image sources
  • connect-src - AJAX, WebSocket, fetch() sources
  • font-src - Font sources
  • media-src - Audio/video sources
  • frame-src - iframe sources

Common Source Values

  • 'self' - Same origin only
  • 'none' - Block all sources
  • 'unsafe-inline' - Allow inline scripts/styles (avoid if possible)
  • 'unsafe-eval' - Allow eval() (avoid if possible)
  • https://example.com - Specific domain
  • https: - Any HTTPS source
  • data: - Data URIs
  • 'nonce-xyz123' - Specific nonce value

Common Configurations

Strict Policy

Content-Security-Policy: default-src 'self'; 
                        script-src 'self'; 
                        style-src 'self' 'unsafe-inline'; 
                        img-src 'self' data:

Development Policy

Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'; 
                        connect-src 'self' ws://localhost:*

CDN-Friendly Policy

Content-Security-Policy: default-src 'self'; 
                        script-src 'self' https://cdn.jsdelivr.net; 
                        style-src 'self' https://fonts.googleapis.com; 
                        font-src https://fonts.gstatic.com

Implementation Example

Express.js

app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy', 
    "default-src 'self'; script-src 'self' https://trusted.com");
  next();
});
// Generate nonce
const nonce = crypto.randomBytes(16).toString('base64');
res.setHeader('Content-Security-Policy', 
  `script-src 'self' 'nonce-${nonce}'`);

// In HTML
<script nonce="${nonce}">console.log('Safe script');</script>

Report-Only Mode

Content-Security-Policy-Report-Only: default-src 'self'
  • Logs violations without blocking
  • Useful for testing before enforcement
  • Reports sent to specified URI

Key Benefits

XSS Prevention: Blocks malicious script injection Data Theft Protection: Controls where data can be sent
Clickjacking Defense: Restricts iframe embedding Mixed Content Protection: Enforces HTTPS usage

Common Gotchas

  • Inline scripts/styles blocked: Use nonces or external files
  • eval() blocked: Avoid dynamic code execution
  • Third-party widgets: May require policy adjustments
  • Legacy code: Often relies on 'unsafe-inline'

Debugging

  1. Check browser console for CSP violation messages
  2. Use Report-Only mode during development
  3. Browser dev tools show blocked resources
  4. Test with strict policy first, then relax as needed

CORS vs CSP

  • CORS: Controls incoming requests to your server
  • CSP: Controls outgoing requests from your webpage

What It Is

Browser security mechanism that allows servers to specify which origins can access their resources, bypassing the same-origin policy.

The Problem

Same-origin policy blocks cross-domain requests by default:

// ❌ Blocked without CORS
// Page: https://myapp.com
fetch('https://api.external.com/data')

How It Works

  1. Browser sends request with Origin header
  2. Server responds with CORS headers (or doesn't)
  3. Browser allows/blocks based on server response

For complex (non-simple) requests, browser sends preflight OPTIONS request first.

Complex/Non-Simple Requeset that triggers OPTIONS preflight

Complex/Non-Simple CORS Requests & OPTIONS Preflight

What is a Non-Simple CORS Request?

A non-simple (or complex) CORS request is any cross-origin request that doesn't meet the criteria for a "simple request". These requests trigger an automatic OPTIONS preflight request from the browser before the actual request is sent.

Simple vs Non-Simple Requests

Simple requests must meet ALL of the following criteria:

  • Method: Only GET, HEAD, or POST
  • Headers: Only CORS-safelisted headers like:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (with specific values only)
    • Origin (automatically added by browsers)
  • Content-Type: Only these values:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

Non-Simple Requests (Triggers Preflight)

Any request that violates the simple request criteria becomes non-simple:

1. Non-Simple HTTP Methods

PUT, DELETE, PATCH, OPTIONS, CONNECT, TRACE

2. Custom or Non-Safelisted Headers

Authorization
X-Requested-With
X-Custom-Header
Content-Type: application/json

3. Non-Simple Content-Types

application/json
application/xml
text/xml

The Preflight Flow

  1. Browser sends OPTIONS request to check permissions
  2. Server responds with allowed methods, headers, and origins
  3. If approved, browser sends the actual request
  4. If denied, browser blocks the request

Examples of Non-Simple Requests

These JavaScript requests would trigger OPTIONS preflight:

// Custom method
fetch('http://api.com/data', { method: 'PUT' })

// JSON content type
fetch('http://api.com/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({})
})

// Custom header
fetch('http://api.com/data', {
  headers: { 'Authorization': 'Bearer token' }
})

⚠️ Important Note About usage curl

curl does NOT send preflight OPTIONS requests. It only sends the exact request you specify. To see the complete CORS preflight behavior, you must test from:

  • A web browser
  • Browser developer tools
  • Testing tools that simulate browser CORS behavior

curl is useful for testing the server's response to individual requests, but won't replicate the full browser CORS flow.

Key Headers

Server must send:

  • Access-Control-Allow-Origin: https://myapp.com (required)
  • Access-Control-Allow-Methods: GET, POST, PUT
  • Access-Control-Allow-Headers: Content-Type, Authorization

Browser automatically sends:

  • Origin: https://myapp.com

Quick Examples

Allow specific origin:

Access-Control-Allow-Origin: https://myapp.com

Allow all (development only):

Access-Control-Allow-Origin: *

Express.js setup:

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://myapp.com');
  next();
});

Key Points

  • Server-side fix only - API being called must send CORS headers
  • Security rule: Never use * with credentials
  • Preflight: Complex requests trigger OPTIONS preflight
  • Localhost binding: Bind server to 127.0.0.1 for local-only access

Common Error

Access to fetch at 'https://api.com' from origin 'https://myapp.com' 
has been blocked by CORS policy

Fix: Configure CORS headers on api.com, not myapp.com


Children
  1. CORS - Cross-Origin Resource Sharing
  2. CSP - Content Security Policy