SalakCode SalakCode
Security

CORS

Cross-Origin Resource Sharing explained

Intermediate
security http api

Definition

CORS (Cross-Origin Resource Sharing) is a security mechanism that allows web pages to request resources from a different domain than the one serving the page. Without CORS, browsers block cross-origin requests by default for security reasons. CORS uses HTTP headers to tell browsers to permit cross-origin access.

The Same-Origin Policy

By default, browsers enforce the Same-Origin Policy:

https://example.com/page1 can request:
✅ https://example.com/page2      (same origin)
❌ https://api.example.com/data   (different subdomain)
❌ http://example.com/page        (different protocol)
❌ https://example.com:8080/page  (different port)
❌ https://other-site.com/page    (different domain)

Two URLs have the same origin only if they share:

  • Protocol (http vs https)
  • Domain (including subdomains)
  • Port

Simple CORS Requests

Some requests don’t trigger CORS preflight:

  • GET, HEAD, POST methods
  • Only specific headers (Accept, Accept-Language, Content-Language, Content-Type)
  • Content-Type only: application/x-www-form-urlencoded, multipart/form-data, text/plain
// Simple GET request
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data));

Server response headers:

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST

Preflight Requests

For non-simple requests, browsers send a preflight OPTIONS request:

// This triggers preflight
fetch('https://api.example.com/data', {
  method: 'DELETE',
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'value'
  }
});

Request flow:

OPTIONS /data HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: X-Custom-Header

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 86400

Then the actual request proceeds.

CORS Headers

Response Headers (Server → Browser)

HeaderDescription
Access-Control-Allow-OriginWhich origins can access (or *)
Access-Control-Allow-MethodsAllowed HTTP methods
Access-Control-Allow-HeadersAllowed custom headers
Access-Control-Allow-CredentialsWhether cookies/auth are allowed
Access-Control-Expose-HeadersWhich response headers JS can access
Access-Control-Max-AgeHow long to cache preflight results

Request Headers (Browser → Server)

HeaderDescription
OriginThe requesting origin (always sent)
Access-Control-Request-MethodMethod for preflight
Access-Control-Request-HeadersHeaders for preflight

Common Errors

1. No Access-Control-Allow-Origin

Access to fetch at 'https://api.example.com/data' from origin 
'https://example.com' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present.

Fix: Server must include Access-Control-Allow-Origin header.

2. Credentials Without Specific Origin

The value of the 'Access-Control-Allow-Origin' header in the 
response must not be the wildcard '*' when the request's 
credentials mode is 'include'.

Fix: Use specific origin, not *, when sending credentials:

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true

3. Preflight Failed

Request header field X-Custom-Header is not allowed by 
Access-Control-Allow-Headers in preflight response.

Fix: Server must whitelist custom headers.

Implementing CORS (Server-Side)

Express.js

// Simple setup
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});

// Or use cors middleware
const cors = require('cors');

// Allow all origins
app.use(cors());

// Restrict to specific origins
app.use(cors({
  origin: ['https://app.example.com', 'https://admin.example.com'],
  credentials: true,
  methods: ['GET', 'POST'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

Nginx

location /api {
  add_header 'Access-Control-Allow-Origin' 'https://example.com' always;
  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
  add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
  add_header 'Access-Control-Allow-Credentials' 'true' always;
  
  # Handle preflight
  if ($request_method = 'OPTIONS') {
    return 204;
  }
}

Credentials and Cookies

To include cookies/auth tokens:

fetch('https://api.example.com/data', {
  credentials: 'include', // Include cookies
  headers: {
    'Authorization': 'Bearer token123'
  }
});

Server must respond with:

Access-Control-Allow-Origin: https://example.com  # Cannot be *
Access-Control-Allow-Credentials: true

Security Considerations

Never Use Wildcard with Credentials

# DANGEROUS - Don't do this!
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

Validate Origins

const allowedOrigins = [
  'https://app.example.com',
  'https://admin.example.com'
];

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (allowedOrigins.includes(origin)) {
    res.header('Access-Control-Allow-Origin', origin);
  }
  next();
});

Consider SameSite Cookies

As an alternative to CORS, consider using SameSite cookies:

Set-Cookie: session=abc123; SameSite=None; Secure
Key Takeaway

CORS is a browser security feature, not a server security feature. Configure it properly: always validate origins instead of using wildcard with credentials, whitelist only necessary headers and methods, and understand that preflight requests add latency. For public APIs, Access-Control-Allow-Origin: * is fine. For authenticated endpoints, always specify exact origins.

Debugging CORS

  1. Check Network Tab: Look for preflight OPTIONS requests
  2. Response Headers: Verify Access-Control-Allow-* headers are present
  3. Credentials Mode: Ensure client and server agree on credentials
  4. Server Logs: Check if preflight requests reach your server

Remember: CORS errors are browser-enforced. The server might respond correctly, but the browser blocks access if headers are missing or incorrect.

Resources

Related Topics