CORS Configuration: Handle Cross-Origin Requests
Learn: CORS Configuration: Handle 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: Handle Cross-Origin Requests & Security Headers
Problem
Modern web applications often need to serve requests from different origins (domains, protocols, ports). Without proper CORS (Cross-Origin Resource Sharing) configuration, browsers block these requests for security reasons. Additionally, missing security headers expose applications to various attacks like XSS, clickjacking, and data theft.
Key Issues:
- Browser blocks cross-origin requests by default
- Sensitive data exposed without proper headers
- Vulnerable to XSS, clickjacking, and MIME-type sniffing attacks
- No protection against credential theft or cache poisoning
Solution
Implement a comprehensive CORS and security headers strategy:
- Configure CORS properly - Allow specific origins, methods, and credentials
- Add security headers - Implement CSP, X-Frame-Options, HSTS, etc.
- Validate requests - Check origin, method, and headers
- Handle preflight requests - Respond to OPTIONS requests correctly
- Environment-based configuration - Different rules for dev/prod
Code Implementation
1. Express.js with CORS Middleware
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const app = express();
// Security headers with Helmet
app.use(helmet());
// Custom CORS configuration
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://example.com',
'https://app.example.com',
'http://localhost:3000' // Development
];
// 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'],
maxAge: 86400 // 24 hours
};
app.use(cors(corsOptions));
// Additional security headers
app.use((req, res, next) => {
// Content Security Policy
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:"
);
// Prevent clickjacking
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
// Prevent MIME-type sniffing
res.setHeader('X-Content-Type-Options', 'nosniff');
// Enable XSS protection
res.setHeader('X-XSS-Protection', '1; mode=block');
// Referrer Policy
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// Permissions Policy (formerly Feature Policy)
res.setHeader(
'Permissions-Policy',
'geolocation=(), microphone=(), camera=()'
);
next();
});
// Handle preflight requests
app.options('*', cors(corsOptions));
// API Routes
app.get('/api/data', (req, res) => {
res.json({ message: 'CORS enabled data', data: [] });
});
app.post('/api/data', (req, res) => {
res.json({ message: 'Data received', status: 'success' });
});
app.listen(3000, () => console.log('Server running on port 3000'));
2. Advanced CORS with Dynamic Origin Validation
const express = require('express');
const app = express();
class CORSManager {
constructor() {
this.allowedOrigins = new Set([
'https://example.com',
'https://app.example.com'
]);
this.trustedDomains = ['example.com'];
}
isOriginAllowed(origin) {
if (!origin) return true; // Allow non-browser requests
// Check exact match
if (this.allowedOrigins.has(origin)) return true;
// Check domain pattern
try {
const url = new URL(origin);
return this.trustedDomains.some(domain =>
url.hostname.endsWith(domain)
);
} catch {
return false;
}
}
addOrigin(origin) {
this.allowedOrigins.add(origin);
}
removeOrigin(origin) {
this.allowedOrigins.delete(origin);
}
getMiddleware() {
return (req, res, next) => {
const origin = req.headers.origin;
if (this.isOriginAllowed(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin || '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader(
'Access-Control-Allow-Methods',
'GET, POST, PUT, DELETE, PATCH, OPTIONS'
);
res.setHeader(
'Access-Control-Allow-Headers',
'Content-Type, Authorization, X-Requested-With'
);
res.setHeader('Access-Control-Max-Age', '86400');
}
// Handle preflight
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
};
}
}
const corsManager = new CORSManager();
app.use(corsManager.getMiddleware());
app.get('/api/secure', (req, res) => {
res.json({ data: 'Secure data' });
});
app.listen(3000);
3. Nginx Configuration
# nginx.conf
server {
listen 80;
server_name api.example.com;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# CORS handling
set $cors_origin "";
set $cors_cred "";
set $cors_methods "";
set $cors_headers "";
if ($http_origin ~* ^(https?://(example\.com|app\.example\.com|localhost:3000))$) {
set $cors_origin $http_origin;
set $cors_cred "true";
set $cors_methods "GET, POST, PUT, DELETE, OPTIONS";
set $cors_headers "Content-Type, Authorization";
}
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials $cors_cred always;
add_header Access-Control-Allow-Methods $cors_methods always;
add_header Access-Control-Allow-Headers $cors_headers always;
add_header Access-Control-Max-Age "86400" always;
# Handle preflight requests
if ($request_method = 'OPTIONS') {
return 204;
}
location /api/ {
proxy_pass http://backend:3000;
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;
}
}
4. Environment-Based Configuration
// config/cors.js
const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';
const corsConfig = {
development: {
origin: ['http://localhost:3000', 'http://localhost:3001', 'http://127.0.0.1:3000'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 3600
},
production: {
origin: [
'https://example.com',
'https://app.example.com',
'https://www.example.com'
],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400
},
staging: {
origin: [
'https://staging.example.com',
'https://staging-app.example.com'
],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400
}
};
module.exports = corsConfig[process.env.NODE_ENV] || corsConfig.development;
5. Client-Side Request Handling
// Client code
async function fetchWithCORS(url, options = {}) {
try {
const response = await fetch(url, {
method: options.method || 'GET',
credentials: 'include', // Include cookies
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`,
...options.headers
},
body: options.body ? JSON.stringify(options.body) : undefined
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('CORS request failed:', error);
throw error;
}
}
// Usage
fetchWithCORS('https://api.example.com/data', {
method: 'POST',
body: { name: 'John' }
}).then(data => console.log(data));
6. Testing CORS Configuration
// test/cors.test.js
const request = require('supertest');
const app = require('../app');
describe('CORS Configuration', () => {
test('should allow requests from allowed origin', async () => {
const response = await request(app)
.get('/api/data')
.set('Origin', 'https://example.com');
expect(response.headers['access-control-allow-origin']).toBe('https://example.com');
expect(response.status).toBe(200);
});
test('should reject requests from disallowed origin', async () => {
const response = await request(app)
.get('/api/data')
.set('Origin', 'https://malicious.com');
expect(response.headers['access-control-allow-origin']).toBeUndefined();
});
test('should handle preflight requests', async () => {
const response = await request(app)
.options('/api/data')
.set('Origin', 'https://example.com')
.set('Access-Control-Request-Method', 'POST');
expect(response.status).toBe(200);
expect(response.headers['access-control-allow-methods']).toContain('POST');
});
test('should include security headers', async () => {
const response = await request(app).get('/api/data');
expect(response.headers['x-frame-options']).toBe('SAMEORIGIN');
expect(response.headers['x-content-type-options']).toBe('nosniff');
expect(response.headers['content-security-policy']).toBeDefined();
});
});
Key Security Headers Explained
| Header | Purpose | Example |
| CORS Headers | Control cross-origin access | Access-Control-Allow-Origin |
| CSP | Prevent XSS attacks | Content-Security-Policy |
| X-Frame-Options | Prevent clickjacking | SAMEORIGIN |
| HSTS | Force HTTPS | Strict-Transport-Security |
| X-Content-Type-Options | Prevent MIME sniffing | nosniff |
| Referrer-Policy | Control referrer info | strict-origin-when-cross-origin |
Best Practices
✅ Whitelist specific origins instead of using *
✅ Use HTTPS in production
✅ Implement HSTS for security
✅ Validate all incoming requests
✅ Use environment-specific configurations
✅ Log CORS rejections for monitoring
✅ Regularly audit allowed origins
✅ Keep security headers updated