CORS - Cross Origin Request Sharing
Cross-Origin Resource Sharing (CORS) is a crucial web security mechanism that controls how web pages from one domain can access resources from another domain. Understanding CORS is essential for modern web development, as it directly impacts how APIs, fonts, images, and other resources can be shared across different origins.
What is CORS?
CORS is a browser security feature that implements the same-origin policy with controlled exceptions. The same-origin policy restricts web pages from making requests to a different domain, protocol, or port than the one serving the web page. CORS provides a way to relax this restriction in a controlled manner.
An "origin" consists of three parts:
- Protocol (http or https)
- Domain (example.com)
- Port (80, 443, 3000, etc.)
Two URLs are considered to have the same origin only if all three components match exactly.
Why CORS Exists
Without CORS, malicious websites could easily make requests to other sites on behalf of users, potentially accessing sensitive information or performing unauthorized actions. CORS ensures that cross-origin requests are only allowed when explicitly permitted by the target server.
How CORS Works
When a web application makes a cross-origin request, the browser adds special CORS headers to the request and checks the response headers to determine if the request should be allowed.
Simple Requests
For simple requests (GET, POST with certain content types), the browser sends the request directly with an Origin header:
GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://myapp.com
The server responds with CORS headers indicating whether the request is allowed:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
Content-Type: application/json
Preflight Requests
For more complex requests (PUT, DELETE, custom headers), the browser first sends a preflight OPTIONS request:
OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://myapp.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
The server responds with allowed methods and headers:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Common CORS Headers
Response Headers (Server to Browser)
Access-Control-Allow-Origin Specifies which origins can access the resource:
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Origin: * # Allow all origins (use with caution)
Access-Control-Allow-Methods Lists allowed HTTP methods:
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers Specifies which headers can be used in requests:
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
Access-Control-Allow-Credentials Indicates whether credentials (cookies, authorization headers) can be included:
Access-Control-Allow-Credentials: true
Access-Control-Max-Age Specifies how long preflight results can be cached:
Access-Control-Max-Age: 3600 # Cache for 1 hour
Request Headers (Browser to Server)
Origin Indicates the origin of the request:
Origin: https://myapp.com
Access-Control-Request-Method Used in preflight requests to indicate the intended method:
Access-Control-Request-Method: PUT
Access-Control-Request-Headers Lists headers that will be used in the actual request:
Access-Control-Request-Headers: Content-Type, Authorization
Practical Examples
Example 1: Basic API Request
Consider a frontend application at https://myapp.com trying to fetch data from https://api.example.com:
// Frontend JavaScript (running on https://myapp.com)
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('CORS error:', error));
For this to work, the API server must include appropriate CORS headers:
// Server-side (Node.js/Express example)
app.get('/users', (req, res) => {
res.header('Access-Control-Allow-Origin', 'https://myapp.com');
res.header('Access-Control-Allow-Methods', 'GET');
res.json([{ id: 1, name: 'John Doe' }]);
});
Example 2: POST Request with Custom Headers
// Frontend making a POST request with authentication
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({ name: 'Jane Doe', email: '[email protected]' })
});
The server needs to handle both the preflight OPTIONS request and the actual POST:
// Handle preflight request
app.options('/users', (req, res) => {
res.header('Access-Control-Allow-Origin', 'https://myapp.com');
res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.sendStatus(200);
});
// Handle actual POST request
app.post('/users', (req, res) => {
res.header('Access-Control-Allow-Origin', 'https://myapp.com');
res.header('Access-Control-Allow-Credentials', 'true');
// Process the request...
res.json({ id: 2, name: 'Jane Doe' });
});
Example 3: Using Express CORS Middleware
Instead of manually setting headers, you can use the CORS middleware:
const cors = require('cors');
// Allow all origins (development only)
app.use(cors());
// Or configure specific origins
const corsOptions = {
origin: ['https://myapp.com', 'https://admin.myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
};
app.use(cors(corsOptions));
Common CORS Issues and Solutions
Issue 1: "Access-Control-Allow-Origin" Error
Problem: Browser blocks request due to missing or incorrect CORS headers.
Solution: Ensure your server includes the correct Access-Control-Allow-Origin header:
// Wrong - will cause CORS errors
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
// When the actual origin is https://localhost:3000
// Correct
res.header('Access-Control-Allow-Origin', 'https://localhost:3000');
Issue 2: Credentials Not Allowed
Problem: Cookies or authentication headers are not being sent.
Solution: Set both client and server to handle credentials:
// Client-side
fetch('https://api.example.com/protected', {
credentials: 'include' // Include cookies
});
// Server-side
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Origin', 'https://myapp.com'); // Cannot use '*' with credentials
Issue 3: Preflight Request Failing
Problem: Complex requests fail because preflight OPTIONS request isn’t handled.
Solution: Implement proper OPTIONS handling:
app.use((req, res, next) => {
if (req.method === 'OPTIONS') {
res.header('Access-Control-Allow-Origin', 'https://myapp.com');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
return res.sendStatus(200);
}
next();
});
Best Practices
1. Be Specific with Origins
Avoid using * for Access-Control-Allow-Origin in production, especially when credentials are involved:
// Good
res.header('Access-Control-Allow-Origin', 'https://trustedapp.com');
// Avoid in production
res.header('Access-Control-Allow-Origin', '*');
2. Minimize Exposed Headers
Only allow headers that are actually needed:
// Only include necessary headers
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
3. Use Environment-Based Configuration
const allowedOrigins = process.env.NODE_ENV === 'production'
? ['https://myapp.com', 'https://admin.myapp.com']
: ['http://localhost:3000', 'http://localhost:3001'];
const corsOptions = {
origin: allowedOrigins,
credentials: true
};
4. Cache Preflight Responses
Set appropriate Access-Control-Max-Age to reduce preflight requests:
res.header('Access-Control-Max-Age', '86400'); // 24 hours
Debugging CORS Issues
When encountering CORS problems:
- Check the browser console for specific error messages
- Inspect network requests to see what headers are being sent and received
- Verify the exact origin (including protocol and port)
- Test with simple requests first before adding complexity
- Use browser developer tools to examine preflight requests
Security Considerations
CORS is a security feature, not a security vulnerability. However, misconfigurations can create security risks:
- Never use
Access-Control-Allow-Origin: *with credentials - Validate and sanitize all incoming data regardless of CORS settings
- Don’t rely on CORS for sensitive operations - implement proper authentication and authorization
- Regularly audit your CORS configuration to ensure it matches your security requirements
Conclusion
CORS is an essential mechanism for enabling secure cross-origin requests in modern web applications. By understanding how CORS works and implementing it correctly, you can build applications that safely share resources across different domains while maintaining security. Remember to always configure CORS headers specifically for your use case rather than using overly permissive settings that could compromise security.