Introduction to CORS
Imagine you're running a restaurant (your web server). By default, you only serve people sitting inside your restaurant (requests from the same origin). But what if someone from the building next door (different origin) wants to order food? That's where CORS comes in - it's like creating a delivery policy that specifies which neighboring buildings you'll deliver to.
CORS (Cross-Origin Resource Sharing) is a security feature implemented by web browsers that controls how web pages in one domain can request and interact with resources from another domain. Without proper CORS configuration, your frontend JavaScript code running on mydomain.com cannot make API requests to api.otherdomain.com.
Project Setup
Let's create a practical example with both a frontend and backend service. Here's our project structure:
cors_demo/
├── frontend/
│ ├── index.html
│ └── app.js
└── backend/
├── package.json
└── server.js
Backend Setup
First, create the backend directory and initialize your Node.js project:
mkdir cors_demo cd cors_demo mkdir backend cd backend npm init -y npm install express cors
Create server.js in your backend folder:
// backend/server.js
const express = require('express');
const cors = require('cors');
const app = express();
// Basic CORS setup
app.use(cors({
origin: 'http://localhost:5500' // Your frontend origin
}));
app.get('/api/data', (req, res) => {
res.json({
message: 'Hello from the backend!',
timestamp: new Date()
});
});
app.listen(3000, () => {
console.log('Backend server running on http://localhost:3000');
});
This is like setting up your restaurant's delivery policy. The cors() middleware is your restaurant's delivery rules, and origin: 'http://localhost:5500' is saying "We only deliver to this specific address."
Frontend Implementation
Create your frontend files:
cd ../ mkdir frontend cd frontend touch index.html app.js
Add this content to index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CORS Demo</title>
</head>
<body>
<h1>CORS Demo</h1>
<button id="fetchData">Fetch Data</button>
<div id="result"></div>
<script src="app.js"></script>
</body>
</html>
And this to app.js:
// frontend/app.js
document.getElementById('fetchData').addEventListener('click', async () => {
try {
const response = await fetch('http://localhost:3000/api/data');
const data = await response.json();
document.getElementById('result').textContent =
`Message: ${data.message}\nTimestamp: ${data.timestamp}`;
} catch (error) {
document.getElementById('result').textContent =
`Error: ${error.message}`;
}
});
Understanding CORS Options
CORS configuration is like creating different delivery policies for your restaurant. Here are common options:
Allow Multiple Origins
app.use(cors({
origin: ['http://localhost:5500', 'https://myapp.com']
}));
This is like saying "We'll deliver to these specific addresses only."
Allow All Origins
app.use(cors({
origin: '*'
}));
This is like saying "We'll deliver anywhere!" (Not recommended for production)
Custom Origin Validation
app.use(cors({
origin: function(origin, callback) {
const allowedOrigins = ['http://localhost:5500', 'https://myapp.com'];
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}));
This is like having a delivery manager check each order's address against an approved list.
Common CORS Headers
CORS headers are like the delivery instructions on your order:
Access-Control-Allow-Origin: Which "addresses" can receive deliveriesAccess-Control-Allow-Methods: What types of "orders" you'll accept (GET, POST, etc.)Access-Control-Allow-Headers: What "special instructions" you'll acceptAccess-Control-Allow-Credentials: Whether you'll accept "membership cards" (cookies)
Example with All Options
app.use(cors({
origin: 'http://localhost:5500',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400 // How long to cache CORS response
}));
Practical Exercise
Let's create a more complex example that demonstrates real-world CORS usage:
Task: Build a Protected API
Create a new file called protected_server.js in your backend folder:
// backend/protected_server.js
const express = require('express');
const cors = require('cors');
const app = express();
// Custom middleware to check API key
const checkApiKey = (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (apiKey === 'your-secret-key') {
next();
} else {
res.status(401).json({ error: 'Invalid API key' });
}
};
// CORS configuration for protected API
app.use(cors({
origin: 'http://localhost:5500',
allowedHeaders: ['Content-Type', 'x-api-key'],
methods: ['GET', 'POST']
}));
// Protected route
app.get('/api/protected', checkApiKey, (req, res) => {
res.json({
message: 'Access granted to protected data',
data: {
id: 123,
content: 'Sensitive information'
}
});
});
app.listen(3001, () => {
console.log('Protected server running on http://localhost:3001');
});
Create a new frontend file called protected.js:
// frontend/protected.js
async function fetchProtectedData() {
try {
const response = await fetch('http://localhost:3001/api/protected', {
headers: {
'x-api-key': 'your-secret-key'
}
});
if (!response.ok) {
throw new Error('Access denied');
}
const data = await response.json();
console.log('Protected data:', data);
} catch (error) {
console.error('Error:', error.message);
}
}
Common CORS Errors and Solutions
Like a restaurant dealing with delivery issues, here are common CORS problems and their solutions:
No 'Access-Control-Allow-Origin' Header
This is like trying to deliver to an address that's not on your approved list. Solution:
// Add the correct origin to your CORS config
app.use(cors({
origin: 'http://yourdomain.com'
}));
Method Not Allowed
This is like trying to place a type of order your restaurant doesn't accept. Solution:
app.use(cors({
methods: ['GET', 'POST', 'PUT', 'DELETE']
}));
Headers Not Allowed
This is like trying to add special instructions that your delivery service doesn't support. Solution:
app.use(cors({
allowedHeaders: ['Content-Type', 'Authorization', 'X-Custom-Header']
}));
Further Topics to Explore
To deepen your understanding of CORS and web security:
- Same-Origin Policy and its importance in web security
- Preflight requests and when they occur
- Security implications of different CORS configurations
- CORS in different environments (development vs. production)
- Alternative approaches like proxy servers
- WebSocket connections and CORS
Real-World Applications
CORS is crucial in many modern web architectures:
- Microservices architectures where different services run on different domains
- Third-party API integrations where your frontend needs to communicate with external services
- Development environments where frontend and backend run on different ports
- Multi-tenant applications where different customers have different domains