Topic Overview

Cross-Origin Resource Sharing (CORS)

Master CORS: how browsers handle cross-origin requests, preflight requests, and security policies for web applications.

Medium10 min read

Cross-Origin Resource Sharing (CORS)

Why This Matters

Think of CORS like a bouncer at a club. The club (server) has a policy about who can enter. If you're from the same neighborhood (same origin), you can enter freely. If you're from a different neighborhood (cross-origin), the bouncer checks the list (CORS headers) to see if you're allowed. CORS does the same for web requests—it's a browser security policy that controls cross-origin requests.

This matters because browsers enforce the same-origin policy by default—scripts from one origin can't access resources from another origin. This prevents attacks (like CSRF), but it also blocks legitimate cross-origin requests. CORS allows servers to specify which origins can access their resources, enabling cross-origin requests while maintaining security.

In interviews, when someone asks "Why can't my frontend call my API?", they're testing whether you understand CORS. Do you know what same-origin means? Do you understand CORS headers? Most engineers don't. They just see CORS errors and don't know how to fix them.

What Engineers Usually Get Wrong

Most engineers think "CORS is a server setting." But CORS is a browser security policy. The server sends CORS headers, and the browser enforces the policy. If CORS headers aren't set correctly, the browser blocks the request. Understanding this helps you configure CORS correctly and debug CORS issues.

Engineers also don't understand preflight requests. For certain requests (like PUT with custom headers), browsers send a preflight OPTIONS request first to check if the actual request is allowed. If the preflight fails, the actual request never happens. Understanding this helps you debug why requests fail even though the endpoint works.

How This Breaks Systems in the Real World

A service had a frontend (example.com) and an API (api.example.com). The frontend tried to call the API, but the browser blocked it (CORS error). The API didn't send CORS headers allowing example.com. The fix? Configure CORS on the API server. Send Access-Control-Allow-Origin: https://example.com header. This allows the frontend to call the API.

Another story: A service was using Access-Control-Allow-Origin: * (allow all origins) for development, but forgot to restrict it in production. This allowed any website to call the API, creating a security risk. The fix? Only allow specific origins in production. Use Access-Control-Allow-Origin: https://yourdomain.com instead of *. This prevents unauthorized cross-origin access.


What is CORS?

CORS enables:

  • Cross-origin requests: Requests from one origin to another
  • Controlled access: Server controls which origins can access resources
  • Security: Prevents unauthorized cross-origin access

Origin: Protocol + Domain + Port

https://example.com:443
http://localhost:3000

Same-origin: Same protocol, domain, and port Cross-origin: Different protocol, domain, or port


Same-Origin Policy

Same-Origin Policy restricts:

  • JavaScript: Can only access resources from same origin
  • AJAX requests: Blocked to different origins
  • Cookies: Not sent to different origins

Why needed:

  • Security: Prevents malicious websites from accessing user data
  • Privacy: Protects user information

Example:

Page: https://example.com
Request to: https://api.example.com
Result: Blocked (different origin)

CORS Headers

Server Response Headers

1. Access-Control-Allow-Origin

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Origin: *  (allow all origins)

2. Access-Control-Allow-Methods

Access-Control-Allow-Methods: GET, POST, PUT, DELETE

3. Access-Control-Allow-Headers

Access-Control-Allow-Headers: Content-Type, Authorization

4. Access-Control-Allow-Credentials

Access-Control-Allow-Credentials: true

5. Access-Control-Max-Age

Access-Control-Max-Age: 3600  (cache preflight for 1 hour)

Client Request Headers

1. Origin

Origin: https://example.com

2. Access-Control-Request-Method

Access-Control-Request-Method: POST

3. Access-Control-Request-Headers

Access-Control-Request-Headers: Content-Type

Simple vs Preflight Requests

Simple Requests

Conditions:

  • Method: GET, POST, or HEAD
  • Headers: Only simple headers (Accept, Content-Language, Content-Type: application/x-www-form-urlencoded, multipart/form-data, text/plain)

Flow:

Browser → Server: GET /api/data
  Origin: https://example.com
  
Server → Browser: Response
  Access-Control-Allow-Origin: https://example.com
  Data: {...}

No preflight: Request sent directly

Preflight Requests

Triggered when:

  • Method: PUT, DELETE, PATCH, or custom methods
  • Headers: Custom headers (Authorization, Content-Type: application/json)

Flow:

Browser → Server: OPTIONS /api/data (preflight)
  Origin: https://example.com
  Access-Control-Request-Method: POST
  Access-Control-Request-Headers: Content-Type
  
Server → Browser: Preflight Response
  Access-Control-Allow-Origin: https://example.com
  Access-Control-Allow-Methods: POST
  Access-Control-Allow-Headers: Content-Type
  
Browser → Server: POST /api/data (actual request)
  Origin: https://example.com
  Content-Type: application/json
  
Server → Browser: Response
  Access-Control-Allow-Origin: https://example.com
  Data: {...}

Two requests: Preflight (OPTIONS) + Actual request


Examples

CORS Configuration

1import express from 'express';
2import cors from 'cors';
3
4const app = express();
5
6// Simple CORS (allow all)
7app.use(cors());
8
9// Custom CORS
10app.use(cors({
11 origin: 'https://example.com',
12 methods: ['GET', 'POST', 'PUT', 'DELETE'],
13 allowedHeaders: ['Content-Type', 'Authorization'],
14 credentials: true,

Manual CORS Headers

1app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
2 const origin = req.headers.origin;
3
4 // Check if origin is allowed
5 const allowedOrigins = ['https://example.com', 'https://app.example.com'];
6
7 if (origin && allowedOrigins.includes(origin)) {
8 res.setHeader('Access-Control-Allow-Origin', origin);
9 res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
10 res

CORS with Credentials

1// Client (browser)
2fetch('https://api.example.com/data', {
3 method: 'POST',
4 credentials: 'include', // Include cookies
5 headers: {
6 'Content-Type': 'application/json'
7 },
8 body: JSON.stringify({ data: 'value' })
9});
10
11// Server
12app.use(cors({
13 origin: 'https://example.com',
14 credentials: true // Allow credentials
15}));

Common Pitfalls

  • Wildcard with credentials: Access-Control-Allow-Origin: * with credentials: true not allowed. Fix: Specify exact origin
  • Not handling preflight: OPTIONS request not handled. Fix: Handle OPTIONS requests, return appropriate headers
  • Missing headers: Not allowing required headers. Fix: Include all custom headers in Access-Control-Allow-Headers
  • CORS errors in console: Browser blocks request. Fix: Check server CORS configuration, verify origin
  • Not caching preflight: Preflight sent for every request. Fix: Set Access-Control-Max-Age

Interview Questions

Beginner

Q: What is CORS and why is it needed?

A:

CORS (Cross-Origin Resource Sharing) allows web pages to make requests to different domains while maintaining security.

Why needed:

  • Same-Origin Policy: Browsers block cross-origin requests by default
  • Security: Prevents malicious websites from accessing user data
  • Controlled access: Server controls which origins can access resources

How it works:

Page: https://example.com
Request to: https://api.example.com

Browser checks: Different origin (cross-origin)
Server responds: Access-Control-Allow-Origin: https://example.com
Browser allows: Request succeeds

Example:

Without CORS:
  Browser blocks: Cross-origin request denied

With CORS:
  Server allows: Access-Control-Allow-Origin: https://example.com
  Browser allows: Request succeeds

CORS Headers:

  • Access-Control-Allow-Origin: Which origins can access
  • Access-Control-Allow-Methods: Which methods allowed
  • Access-Control-Allow-Headers: Which headers allowed

Intermediate

Q: Explain the difference between simple and preflight requests. When is each used?

A:

Simple Requests:

Conditions:

  • Method: GET, POST, or HEAD
  • Headers: Only simple headers (Accept, Content-Language, Content-Type: application/x-www-form-urlencoded, multipart/form-data, text/plain)

Flow:

Browser → Server: GET /api/data
  Origin: https://example.com
  
Server → Browser: Response
  Access-Control-Allow-Origin: https://example.com

No preflight: Request sent directly

Preflight Requests:

Triggered when:

  • Method: PUT, DELETE, PATCH, or custom methods
  • Headers: Custom headers (Authorization, Content-Type: application/json)

Flow:

1. Browser → Server: OPTIONS /api/data (preflight)
     Origin: https://example.com
     Access-Control-Request-Method: POST
     Access-Control-Request-Headers: Content-Type
   
2. Server → Browser: Preflight Response
     Access-Control-Allow-Origin: https://example.com
     Access-Control-Allow-Methods: POST
     Access-Control-Allow-Headers: Content-Type
   
3. Browser → Server: POST /api/data (actual request)
     Origin: https://example.com
     Content-Type: application/json
   
4. Server → Browser: Response
     Access-Control-Allow-Origin: https://example.com

Two requests: Preflight (OPTIONS) + Actual request

Why preflight:

  • Safety check: Browser checks if server allows request before sending
  • Protection: Prevents unauthorized requests

Senior

Q: Design a CORS system for a multi-tenant API that serves multiple client applications. How do you handle different origins, credentials, and security?

A:

1class MultiTenantCORSSystem {
2 private allowedOrigins: Map<string, string[]>; // tenant → origins
3 private corsCache: Map<string, CORSConfig>;
4
5 constructor() {
6 this.allowedOrigins = new Map();
7 this.corsCache = new Map();
8 }
9
10 // 1. Dynamic CORS Configuration
11 async handleCORS(req: Request, res: Response): Promise<void>

Features:

  1. Multi-tenant: Different CORS configs per tenant
  2. Dynamic origins: Load from database
  3. Credentials: Handle cookies/auth properly
  4. Preflight caching: Cache preflight responses
  5. Security: Origin validation, rate limiting

  • HTTP/1 vs HTTP/2 vs HTTP/3 - CORS is an HTTP security mechanism, understanding HTTP explains CORS implementation

  • TLS/SSL Handshake - HTTPS uses TLS, CORS works with HTTPS, understanding TLS helps configure CORS for secure connections

  • OSI Model (7 Layers) - CORS operates at Layer 7 (Application), understanding the OSI model provides context

  • Proxy vs Reverse Proxy - Reverse proxies can handle CORS, understanding proxies helps configure CORS at the edge

  • WebSockets - WebSockets can have CORS considerations, understanding CORS helps configure WebSocket connections

  • CORS: Browser security mechanism for cross-origin requests

  • Same-origin policy: Browsers block cross-origin requests by default

  • Simple requests: GET, POST, HEAD with simple headers (no preflight)

  • Preflight requests: OPTIONS request before actual request (for PUT, DELETE, custom headers)

  • CORS headers: Access-Control-Allow-Origin, Access-Control-Allow-Methods, etc.

  • Credentials: Cannot use wildcard (*) with credentials, must specify origin

  • Security: Validate origins, use whitelist, prevent abuse

  • Best practices: Handle preflight, cache preflight, specify exact origins with credentials

Key Takeaways

CORS: Browser security mechanism for cross-origin requests

Same-origin policy: Browsers block cross-origin requests by default

Simple requests: GET, POST, HEAD with simple headers (no preflight)

Preflight requests: OPTIONS request before actual request (for PUT, DELETE, custom headers)

CORS headers: Access-Control-Allow-Origin, Access-Control-Allow-Methods, etc.

Credentials: Cannot use wildcard (*) with credentials, must specify origin

Security: Validate origins, use whitelist, prevent abuse

Best practices: Handle preflight, cache preflight, specify exact origins with credentials


About the author

InterviewCrafted helps you master system design with patience. We believe in curiosity-led engineering, reflective writing, and designing systems that make future changes feel calm.