Topic Overview
Cross-Origin Resource Sharing (CORS)
Master CORS: how browsers handle cross-origin requests, preflight requests, and security policies for web applications.
CORS (Cross-Origin Resource Sharing) is a browser security mechanism that allows web pages to make requests to a different domain than the one serving the web page, while maintaining security.
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 (Express.js)
const express = require('express');
const cors = require('cors');
const app = express();
// Simple CORS (allow all)
app.use(cors());
// Custom CORS
app.use(cors({
origin: 'https://example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 3600
}));
// Per-route CORS
app.get('/api/data', cors({
origin: 'https://example.com'
}), (req, res) => {
res.json({ data: 'success' });
});
Manual CORS Headers
app.use((req, res, next) => {
const origin = req.headers.origin;
// Check if origin is allowed
const allowedOrigins = ['https://example.com', 'https://app.example.com'];
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
// Handle preflight
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
next();
});
CORS with Credentials
// Client (browser)
fetch('https://api.example.com/data', {
method: 'POST',
credentials: 'include', // Include cookies
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: 'value' })
});
// Server
app.use(cors({
origin: 'https://example.com',
credentials: true // Allow credentials
}));
Common Pitfalls
- Wildcard with credentials:
Access-Control-Allow-Origin: *withcredentials: truenot 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 accessAccess-Control-Allow-Methods: Which methods allowedAccess-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:
class MultiTenantCORSSystem {
private allowedOrigins: Map<string, string[]>; // tenant → origins
private corsCache: Map<string, CORSConfig>;
constructor() {
this.allowedOrigins = new Map();
this.corsCache = new Map();
}
// 1. Dynamic CORS Configuration
async handleCORS(req: Request, res: Response): Promise<void> {
const origin = req.headers.origin;
const tenant = this.extractTenant(req);
// Get allowed origins for tenant
const allowed = this.getAllowedOrigins(tenant);
if (allowed.includes(origin)) {
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Tenant-ID');
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Max-Age', '3600');
}
// Handle preflight
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
}
// 2. Tenant-Based Origin Management
getAllowedOrigins(tenantId: string): string[] {
// Get from database or cache
if (!this.allowedOrigins.has(tenantId)) {
const origins = this.loadOriginsFromDatabase(tenantId);
this.allowedOrigins.set(tenantId, origins);
}
return this.allowedOrigins.get(tenantId);
}
// 3. CORS with Credentials
handleCredentials(req: Request, res: Response): void {
const origin = req.headers.origin;
// Cannot use wildcard with credentials
if (this.isAllowedOrigin(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin); // Specific origin
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
}
// 4. Preflight Caching
cachePreflight(origin: string, config: CORSConfig): void {
const key = `${origin}:${config.methods}:${config.headers}`;
this.corsCache.set(key, {
...config,
cachedUntil: Date.now() + 3600000 // 1 hour
});
}
// 5. Security: Origin Validation
validateOrigin(origin: string): boolean {
// Check against whitelist
// Validate format
// Check for malicious patterns
return this.isValidOrigin(origin);
}
// 6. Rate Limiting for Preflight
rateLimitPreflight(origin: string): boolean {
// Prevent preflight abuse
const count = this.getPreflightCount(origin);
return count < 100; // Max 100 preflights per hour
}
}
Features:
- Multi-tenant: Different CORS configs per tenant
- Dynamic origins: Load from database
- Credentials: Handle cookies/auth properly
- Preflight caching: Cache preflight responses
- Security: Origin validation, rate limiting
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