CORS Complete Guide
이 글의 핵심
CORS (Cross-Origin Resource Sharing) is a security mechanism that controls which origins can access your API. Understanding CORS is essential for building secure web applications.
Introduction
CORS (Cross-Origin Resource Sharing) is a security feature implemented by browsers to control how web pages can request resources from different origins.
What is an Origin?
An origin consists of:
- Protocol:
httporhttps - Domain:
example.com - Port:
:3000
https://example.com:3000
└─┬──┘ └────┬─────┘└┬─┘
│ │ └─ Port
│ └───────── Domain
└─────────────────── Protocol
Same origin:
https://example.com/api/users
https://example.com/api/posts
Different origins (CORS applies):
http://localhost:3000 → http://localhost:8000
https://example.com → https://api.example.com
https://example.com → https://example.com:3000
1. The Problem
Without CORS, browsers block cross-origin requests:
// Frontend at http://localhost:3000
fetch('http://localhost:8000/api/users')
.then(res => res.json())
.catch(err => console.error(err));
// Error:
// Access to fetch at 'http://localhost:8000/api/users'
// from origin 'http://localhost:3000' has been blocked by CORS policy
2. Basic Setup
npm install cors
const express = require('express');
const cors = require('cors');
const app = express();
// Enable CORS for all origins (not recommended for production)
app.use(cors());
app.get('/api/users', (req, res) => {
res.json({ users: ['Alice', 'Bob'] });
});
app.listen(8000);
3. Specific Origins
// Allow single origin
app.use(cors({
origin: 'http://localhost:3000'
}));
// Allow multiple origins
const allowedOrigins = [
'http://localhost:3000',
'https://example.com',
'https://www.example.com'
];
app.use(cors({
origin: (origin, callback) => {
// Allow requests with no origin (like mobile apps, Postman)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}));
4. Full Configuration
app.use(cors({
origin: 'http://localhost:3000',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Total-Count'],
credentials: true, // Allow cookies
maxAge: 3600, // Cache preflight response for 1 hour
}));
5. Credentials (Cookies)
// Server
app.use(cors({
origin: 'http://localhost:3000',
credentials: true, // Allow cookies
}));
app.get('/api/profile', (req, res) => {
// Can now read cookies
res.json({ user: req.user });
});
// Client
fetch('http://localhost:8000/api/profile', {
credentials: 'include', // Send cookies
})
.then(res => res.json());
6. Preflight Requests
For complex requests (PUT, DELETE, custom headers), browsers send a preflight OPTIONS request:
// Automatic preflight handling
app.use(cors());
// Manual preflight handling
app.options('/api/users', cors());
app.put('/api/users/:id', cors(), (req, res) => {
// Update user
});
7. Dynamic Origins
app.use(cors({
origin: (origin, callback) => {
// Allow all subdomains
if (!origin || origin.match(/\.example\.com$/)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}));
// Or check database
app.use(cors({
origin: async (origin, callback) => {
const allowed = await db.allowedOrigins.findOne({ origin });
callback(null, !!allowed);
}
}));
8. Route-Specific CORS
// Enable CORS for specific routes
app.get('/api/public', cors(), (req, res) => {
res.json({ data: 'public' });
});
// No CORS for this route
app.get('/api/private', (req, res) => {
res.json({ data: 'private' });
});
// Different CORS config per route
app.get('/api/admin', cors({
origin: 'https://admin.example.com'
}), (req, res) => {
res.json({ data: 'admin' });
});
9. Custom Headers
app.use(cors({
origin: 'http://localhost:3000',
allowedHeaders: [
'Content-Type',
'Authorization',
'X-Requested-With',
'X-Custom-Header'
],
exposedHeaders: [
'X-Total-Count',
'X-Page-Count'
]
}));
// Client can now read exposed headers
fetch('http://localhost:8000/api/users')
.then(res => {
const totalCount = res.headers.get('X-Total-Count');
return res.json();
});
10. Environment-Based Configuration
const corsOptions = {
origin: process.env.NODE_ENV === 'production'
? 'https://example.com'
: 'http://localhost:3000',
credentials: true,
};
app.use(cors(corsOptions));
11. Common Errors and Solutions
Error 1: No ‘Access-Control-Allow-Origin’ header
// Problem: CORS not enabled
app.get('/api/users', (req, res) => {
res.json({ users: [] });
});
// Solution: Add CORS
app.use(cors());
Error 2: Credentials not supported with ’*‘
// Problem: Can't use credentials with wildcard origin
app.use(cors({
origin: '*',
credentials: true, // ❌ Error!
}));
// Solution: Specify exact origins
app.use(cors({
origin: 'http://localhost:3000',
credentials: true, // ✅ Works
}));
Error 3: Method not allowed
// Problem: Method not in allowed methods
app.use(cors({
methods: ['GET', 'POST'], // PUT not allowed
}));
app.put('/api/users/:id', (req, res) => {
// CORS error!
});
// Solution: Add method
app.use(cors({
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
Error 4: Header not allowed
// Problem: Custom header not allowed
fetch('http://localhost:8000/api/users', {
headers: {
'X-Custom-Header': 'value' // Not allowed!
}
});
// Solution: Add to allowedHeaders
app.use(cors({
allowedHeaders: ['Content-Type', 'X-Custom-Header'],
}));
12. Production Configuration
const express = require('express');
const cors = require('cors');
const app = express();
// Production-ready CORS
const allowedOrigins = [
'https://example.com',
'https://www.example.com',
'https://app.example.com',
];
app.use(cors({
origin: (origin, callback) => {
// Allow requests with no origin (mobile apps, curl, Postman)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
console.warn(`CORS blocked: ${origin}`);
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Total-Count'],
credentials: true,
maxAge: 86400, // 24 hours
optionsSuccessStatus: 200,
}));
app.listen(process.env.PORT || 8000);
13. With Authentication
const express = require('express');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const app = express();
app.use(cors({
origin: 'http://localhost:3000',
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization'],
}));
// Protected route
app.get('/api/profile', (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
res.json({ user: decoded });
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
});
// Client
fetch('http://localhost:8000/api/profile', {
headers: {
'Authorization': `Bearer ${token}`,
},
credentials: 'include',
});
14. CORS Proxy (Development)
For development, you can proxy requests:
// Next.js (next.config.js)
module.exports = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://localhost:8000/api/:path*',
},
];
},
};
// Vite (vite.config.js)
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
},
},
},
};
15. Testing CORS
// Test CORS with curl
curl -H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
-X OPTIONS \
http://localhost:8000/api/users
// Check response headers
// Access-Control-Allow-Origin: http://localhost:3000
// Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Summary
CORS controls cross-origin access:
- Security mechanism implemented by browsers
- Origin checking prevents unauthorized access
- Credentials support for cookies/auth
- Preflight requests for complex operations
- Production config must be restrictive
Key Takeaways:
- Never use
origin: '*'in production - Enable credentials only for trusted origins
- Specify allowed methods and headers
- Test CORS in production-like environment
- Use proxies for development
Next Steps:
- Secure with Helmet
- Auth with Passport
- Build Express API
Resources:
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Complete CORS guide for Node.js and Express. Learn what CORS is, how to configure it, handle preflight requests, and fix…
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
Keywords Covered in This Article (Related Search Terms)
This article covers CORS, Security, Node.js, Express, Backend, HTTP.