Broswers enforce the same-origin policy as a security measure, restricting web pages from making requests to a different origin (scheme, host, or port). This restriction often blocks legitimate cross-origin data access, which is where Cross-Origin Resource Sharing (CORS) comes into play. CORS is a mechanism that allows servers to explicitly whitelist certain origins, methods, and headers, enabling secure cross-origin communication.
Core CORS Headers
CORS relies on specific HTTP response headers:
Access-Control-Allow-Origin– defines permitted origins. Use a specific origin (e.g.,https://example.com) or*to allow all origins.Access-Control-Allow-Methods– lists allowed HTTP methods (e.g.,GET, POST, PUT).Access-Control-Allow-Headers– indicates which request headers can be used (e.g.,Content-Type, Authorization).Access-Control-Allow-Credentials– iftrue, the browser includes credentials (cookies, authorization headers) in the request.Access-Control-Expose-Headers– specifies which response headers are accessible to client scripts.
Setting Up CORS on the Server (Node.js with Express)
Below is an Express middleware that attaches the necessary CORS headers:
const express = require('express');
const app = express();
app.use((req, res, next) => {
// Allow all origins (for development only; restrict in production)
res.setHeader('Access-Control-Allow-Origin', '*');
// Specify allowed methods
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
// Specify allowed headers
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// Enable credentials if needed
// res.setHeader('Access-Control-Allow-Credentials', 'true');
// Handle preflight requests
if (req.method === 'OPTIONS') {
return res.status(200).end();
}
next();
});
app.get('/api/data', (req, res) => {
res.json({ message: 'Cross-origin response works!' });
});
app.listen(4000, () => {
console.log('Server listening on port 4000');
});
Making CORS Requests from the Client
Using fetch with credentials:
const endpoint = 'http://localhost:4000/api/data';
fetch(endpoint, {
method: 'GET',
credentials: 'include', // sends cookies with the request
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => console.log('Data:', data))
.catch(err => console.error('Fetch error:', err));
Using XMLHttpRequest:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:4000/api/data', true);
xhr.withCredentials = true; // required when Access-Control-Allow-Credentials is true
xhr.onload = function () {
if (xhr.status === 200) {
console.log('Response:', JSON.parse(xhr.responseText));
}
};
xhr.send();
Handling Preflight Requests
When a request uses a method other than simple methods (GET, HEAD, POST) or includes custom headers, the browser first sends an OPTIONS (preflight) request. The server must respond appropriately:
app.options('/api/data', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', 'https://yourdomain.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header');
res.status(204).end();
});
Common Pitfalls
- Credentials mismatch: If
Access-Control-Allow-Credentialsistrue,Access-Control-Allow-Origincannot be*; it must specify an explicit origin. - Missing preflight handler: Ensure your server responds to
OPTIONSrequests with the appropriate CORS headers. - Overly permissive origins: Avoid using
*in production; specify exact domains to reduce security risks.
CORS provides a controlled way to relax the same-origin policy while maintaining security. By configuring the correct headers and handling preflight requests, developers can integrate cross-origin data flows seamlessyl into their applications.