CORS Configuration: Cross-Origin Requests
Learn: CORS Configuration: Cross-Origin Requests
Welcome to TopperBlog! 👋
I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.
🎯 What I Write About:
• AI/ML Engineering & LLMs
• Web3 & Blockchain Development
• System Design & Architecture
• Interview Preparation (FAANG)
• Freelancing & Remote Work
• Modern Tech Stacks (Next.js, React, Rust, TypeScript)
• Performance Optimization & Best Practices
💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.
📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.
🌐 Let's connect and grow together in this amazing tech journey!
#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering
CORS Configuration: Cross-Origin Requests, Preflight, and Headers
Problem
When building modern web applications, frontend code running on one domain often needs to request resources from APIs on different domains. Browsers enforce the Same-Origin Policy for security, blocking these cross-origin requests by default. This creates friction in development and production environments where:
- Frontend (example.com) cannot fetch from API (api.example.com)
- Single-page applications cannot communicate with third-party services
- Mobile apps wrapping web content face authentication issues
- Microservices architectures struggle with domain separation
Without proper CORS (Cross-Origin Resource Sharing) configuration, legitimate requests fail silently or throw cryptic errors, leaving developers confused about what went wrong.
Solution
CORS is a browser-based security mechanism that allows servers to explicitly grant cross-origin access through HTTP headers. The solution involves:
Understanding the Same-Origin Policy: Origins are defined by protocol, domain, and port (https://api.example.com:443 ≠ https://api.example.com:8443)
Implementing Preflight Requests: For complex requests, browsers automatically send OPTIONS requests to verify server permissions before sending the actual request
Configuring Response Headers: Servers respond with specific headers that tell browsers which origins, methods, and headers are allowed
Handling Credentials: Special configuration is needed when requests include cookies or authentication headers
Server-Side Implementation: Configure CORS at the application level, not just at the reverse proxy
Code
Node.js/Express
const express = require('express');
const cors = require('cors');
const app = express();
// Basic CORS - Allow all origins
app.use(cors());
// Detailed CORS configuration
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://example.com',
'https://app.example.com',
'http://localhost:3000',
'http://localhost:5173'
];
// Allow requests with no origin (like mobile apps or curl requests)
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true, // Allow cookies/auth headers
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
exposedHeaders: ['X-Total-Count', 'X-Page-Number'], // Headers client can access
maxAge: 86400 // Preflight cache duration (24 hours)
};
app.use(cors(corsOptions));
// Handle preflight requests explicitly
app.options('*', cors(corsOptions));
// Or for specific routes
app.options('/api/users', cors(corsOptions));
app.get('/api/users', (req, res) => {
res.json({ users: [] });
});
// Manual CORS headers (if not using middleware)
app.get('/api/data', (req, res) => {
res.header('Access-Control-Allow-Origin', 'https://example.com');
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Max-Age', '86400');
res.json({ data: 'sensitive' });
});
app.listen(3001, () => console.log('API running on port 3001'));
Python/Flask
from flask import Flask, jsonify
from flask_cors import CORS, cross_origin
app = Flask(__name__)
# Basic CORS - Allow all origins
CORS(app)
# Detailed CORS configuration
cors_config = {
"origins": [
"https://example.com",
"https://app.example.com",
"http://localhost:3000"
],
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
"allow_headers": ["Content-Type", "Authorization", "X-Requested-With"],
"expose_headers": ["X-Total-Count", "X-Page-Number"],
"supports_credentials": True,
"max_age": 86400
}
CORS(app, resources={
r"/api/*": cors_config
})
# Decorator approach for specific routes
@app.route('/api/users', methods=['GET', 'POST', 'OPTIONS'])
@cross_origin(
origins=["https://example.com", "http://localhost:3000"],
methods=["GET", "POST"],
allow_headers=["Content-Type", "Authorization"],
supports_credentials=True,
max_age=86400
)
def get_users():
return jsonify({"users": []})
# Manual headers
@app.route('/api/data')
def get_data():
response = jsonify({"data": "sensitive"})
response.headers['Access-Control-Allow-Origin'] = 'https://example.com'
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
response.headers['Access-Control-Max-Age'] = '86400'
return response
if __name__ == '__main__':
app.run(port=3001, debug=True)
Python/Django
# settings.py
INSTALLED_APPS = [
# ...
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
# ... other middleware
]
CORS_ALLOWED_ORIGINS = [
"https://example.com",
"https://app.example.com",
"http://localhost:3000",
"http://localhost:5173",
]
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_METHODS = [
"DELETE",
"GET",
"OPTIONS",
"PATCH",
"POST",
"PUT",
]
CORS_ALLOW_HEADERS = [
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
]
CORS_EXPOSE_HEADERS = [
"x-total-count",
"x-page-number",
]
CORS_MAX_AGE = 86400
# views.py
from django.http import JsonResponse
from corsheaders.decorators import ensure_csrf_cookie
@ensure_csrf_cookie
def get_users(request):
if request.method == 'OPTIONS':
response = JsonResponse({})
else:
response = JsonResponse({"users": []})
response['Access-Control-Allow-Origin'] = 'https://example.com'
response['Access-Control-Allow-Credentials'] = 'true'
return response
Java/Spring Boot
// Configuration class
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins(
"https://example.com",
"https://app.example.com",
"http://localhost:3000"
)
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("X-Total-Count", "X-Page-Number")
.allowCredentials(true)
.maxAge(86400);
}
}
// Controller with annotation
import org.springframework.web.bind.annotation.*;
import org.springframework.web.cors.CorsConfiguration;
@RestController
@RequestMapping("/api")
@CrossOrigin(
origins = {"https://example.com", "http://localhost:3000"},
methods = {RequestMethod.GET, RequestMethod.POST},
allowedHeaders = {"Content-Type", "Authorization"},
exposedHeaders = {"X-Total-Count"},
allowCredentials = "true",
maxAge = 86400
)
public class UserController {
@GetMapping("/users")
public ResponseEntity<?> getUsers() {
return ResponseEntity.ok(new Users());
}
}
// application.yml
spring:
web:
cors:
allowed-origins: https://example.com,http://localhost:3000
allowed-methods: GET,POST,PUT,DELETE,PATCH,OPTIONS
allowed-headers: "*"
exposed-headers: X-Total-Count,X-Page-Number
allow-credentials: true
max-age: 86400
Nginx (Reverse Proxy)
# nginx.conf
upstream api_backend {
server localhost:3001;
}
server {
listen 80;
server_name api.example.com;
# Handle preflight requests
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With';
add_header 'Access-Control-Max-Age' '86400';
add_header 'Content-Length' '0';
return 204;
}
# Regular requests
proxy_pass http://api_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# CORS headers
add_header 'Access-Control-Allow-Origin' 'https://example.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Expose-Headers' 'X-Total-Count, X-Page-Number' always;
}
}
Frontend/JavaScript
// Simple fetch with CORS
fetch('https://api.example.com/users', {
method: 'GET',
credentials: 'include', // Include cookies
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('CORS error:', error));
// Axios with CORS
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'https://api.example.com',
withCredentials: true, // Include cookies
headers: {
'Content-Type': 'application/json'
}
});
apiClient.get('/users')
.then(response => console.log(response.data))
.catch(error => console.error('CORS error:', error));
// React with custom hook
import { useEffect, useState } from 'react';
function useApi(url) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url, {
credentials: 'include',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
})
.then(res => res.json())
.then(data => setData(data))
.catch(err => setError(err));
}, [url]);
return { data, error };
}
Tips
1. Understand Preflight Requests
- Browsers automatically send OPTIONS requests for "complex" requests (POST, PUT, DELETE, or requests with custom headers)
- Preflight results are cached based on
Access-Control-Max-Ageheader - Set reasonable cache times (3600-86400 seconds) to reduce preflight overhead
- Simple requests (GET, HEAD, POST with standard headers) don't trigger preflight
2. Credentials and Authentication
- Set
credentials: 'include'on frontend ANDAccess-Control-Allow-Credentials: trueon backend - When credentials are enabled,
Access-Control-Allow-Origincannot be*(must be specific origin) - Cookies are only sent if both sides explicitly allow it
- Authorization headers require explicit allowance in
Access-Control-Allow-Headers
3. Security Best Practices
- Never use
Access-Control-Allow-Origin: *withcredentials: true - Whitelist specific origins instead of allowing all
- Use environment variables for allowed origins (different per environment)
- Validate origin on backend, don't blindly echo the
Originheader - Restrict HTTP methods to what's actually needed
- Only expose headers that clients need via
Access-Control-Expose-Headers
4. Common Mistakes
- Forgetting to handle OPTIONS requests (preflight fails silently)
- Using
*for origins when credentials are needed - Not setting
credentials: 'include'on frontend when backend requires it - Exposing sensitive headers unnecessarily
- Configuring CORS only at reverse proxy, not at application level
- Not testing with actual cross-origin requests (localhost:3000 → localhost:3001)
5. Debugging CORS Issues
// Check browser console for CORS errors
// Look for: "Access to XMLHttpRequest at 'X' from origin 'Y' has been blocked by CORS policy"
// Use curl to test backend CORS headers
curl -H "Origin: https://example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
-X OPTIONS https://api.example.com/users -v
// Check response headers for CORS configuration
curl -H "Origin: https://example.com" \
https://api.example.com/users -v
6. Environment-Specific Configuration
// Development
const allowedOrigins = [
'http://localhost:3000',
'http://localhost:5173',
'http://127.0.0.1:3000'
];
// Production
const allowedOrigins = [
'https://example.com',
'https://app.example.com'
];
// Dynamic based on NODE_ENV
const corsOrigins = process.env.NODE_ENV === 'production'
? productionOrigins
: developmentOrigins;
7. Testing CORS Configuration
// Test script
async function testCors() {
const origins = [
'https://example.com',
'https://app.example.com',
'https://unauthorized.com'
];
for (const origin of origins) {
try {
const response = await fetch('https://api.example.com/users', {
headers: { 'Origin': origin }
});
console.log(`${origin}: ${response.status}`);
} catch (e) {
console.log(`${origin}: BLOCKED`);
}
}
}
8. Performance Optimization
- Increase
Access-Control-Max-Ageto cache preflight responses (reduces requests) - Use simple requests when possible (GET, HEAD, POST with standard headers)
- Combine multiple API calls to reduce preflight overhead
- Consider using a single endpoint for multiple operations
- Monitor preflight requests in production (they add latency)
9. Wildcard Patterns
// Some frameworks support patterns
const corsOptions = {
origin: /\.example\.com$/, // Matches *.example.com
// or
origin: function(origin, callback) {
if (origin && origin.endsWith('.example.com')) {
callback(null, true);
} else {
callback(new Error('Not allowed'));
}
}
};
10. CORS vs. JSONP
- CORS is the modern standard (use this)
- JSONP is legacy workaround (avoid unless supporting very old browsers)
- CORS works with all HTTP methods; JSONP only works with GET
- CORS supports credentials; JSONP doesn't
Summary
CORS is essential for modern web development. Implement it by:
- Configuring allowed origins (whitelist specific domains)
- Handling preflight OPTIONS requests
- Setting appropriate response headers
- Managing credentials carefully
- Testing thoroughly across environments
Proper CORS configuration balances security with functionality, enabling legitimate cross-origin communication while protecting against unauthorized access.