Cross-Origin Resource Sharing (CORS) is a security feature implemented by web browsers to control how resources are requested from a different domain, protocol, or port than the one serving the web page. This mechanism is crucial for web applications, as it helps prevent unauthorized access to resources by enforcing security policies. In the context of Express.js, understanding and configuring CORS is essential when building APIs that interact with client-side applications hosted on different domains.
CORS stands for Cross-Origin Resource Sharing. It’s a security feature built into web browsers that restricts web pages from making requests to a domain different from the one that served the web page. This is known as a “same-origin policy.” However, modern web applications often need to interact with resources from other domains, such as APIs. CORS provides a way to relax the same-origin policy, allowing secure cross-origin requests.
Imagine you’re building an API on api.example.com
and a front-end application on app.example.com
. By default, the browser’s same-origin policy would block any requests from app.example.com
to api.example.com
. CORS enables api.example.com
to specify which domains are allowed to access its resources, thus enabling safe cross-origin communication.
CORS works by using specific HTTP headers that dictate how resources should be accessed across origins. The key headers include:
Access-Control-Allow-Origin
: Specifies which origin(s) are allowed to access the resource.Access-Control-Allow-Methods
: Lists the HTTP methods (e.g., GET, POST) that the server permits.Access-Control-Allow-Headers
: Specifies which headers can be sent with the request.Access-Control-Allow-Credentials
: Indicates whether credentials (e.g., cookies) can be included in the request.Access-Control-Max-Age
: Specifies how long the results of a preflight request can be cached.For certain types of requests, the browser first sends a “preflight” request using the HTTP OPTIONS method to determine whether the actual request is safe to send. This happens automatically for requests that are not simple (e.g., POST requests with custom headers). The server must respond to the preflight request with appropriate CORS headers.
Before diving into CORS configuration, let’s set up a basic Express.js application. If you don’t have Express.js installed, you can install it using npm:
npm install express
app.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
cors
MiddlewareExpress.js makes it easy to handle CORS by using the cors
middleware, which can be installed via npm:
npm install cors
You can then include it in your Express.js application to automatically handle CORS requests.
app.js
(continued)
const cors = require('cors');
app.use(cors()); // Enable CORS for all routes
app.get('/', (req, res) => {
res.send('CORS-enabled route');
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
By default, the cors
middleware allows requests from any origin. However, you can configure it to allow only specific origins:
app.js
(continued)
const corsOptions = {
origin: 'http://example.com', // Allow only this origin
methods: ['GET', 'POST'], // Allow only specific methods
allowedHeaders: ['Content-Type', 'Authorization'], // Allow specific headers
};
app.use(cors(corsOptions));
app.get('/', (req, res) => {
res.send('CORS with specific configuration');
});
origin
: Specifies that only requests from http://example.com
are allowed.methods
: Limits the allowed HTTP methods to GET and POST.allowedHeaders
: Specifies which headers are allowed in the request.For non-simple requests (e.g., those involving custom headers or methods like PUT or DELETE), the browser sends a preflight request. The cors
middleware automatically handles preflight requests, but you can also configure it explicitly.
app.js
(continued)
const corsOptions = {
origin: 'http://example.com',
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
optionsSuccessStatus: 200, // Default is 204
};
app.use(cors(corsOptions));
app.options('/api', cors(corsOptions)); // Handle preflight requests
app.post('/api', (req, res) => {
res.send('API endpoint with CORS');
});
optionsSuccessStatus
: Sets the status code for successful OPTIONS requests. The default status is 204, but some legacy browsers may require a 200 status code.If your API needs to support multiple origins, you can configure cors
to allow them dynamically:
app.js
(continued)
const allowedOrigins = ['http://example.com', 'http://anotherdomain.com'];
const corsOptions = {
origin: function (origin, callback) {
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
};
app.use(cors(corsOptions));
origin
function checks if the request’s origin is in the allowed list and approves or denies the request accordingly.Sometimes, you might want to enable CORS only for specific routes, rather than the entire application:
app.js
(continued)
const specificCorsOptions = {
origin: 'http://example.com',
};
app.get('/public', cors(specificCorsOptions), (req, res) => {
res.send('This route is CORS-enabled for specific origin');
});
app.get('/private', (req, res) => {
res.send('This route is not CORS-enabled');
});
/public
route is CORS-enabled for requests from http://example.com
, while the /private
route is not.If your application requires sending cookies or HTTP authentication information with cross-origin requests, you need to enable credentials in your CORS configuration.
app.js
(continued)
const corsOptions = {
origin: 'http://example.com',
credentials: true, // Allow credentials
};
app.use(cors(corsOptions));
app.get('/protected', (req, res) => {
res.send('Protected route with credentials');
});
credentials: true
, the server allows browsers to include cookies and HTTP authentication information in cross-origin requests.Only allow trusted origins to access your resources. Avoid using *
(wildcard) for the Access-Control-Allow-Origin
header, especially when dealing with sensitive data.
Minimize the attack surface by limiting the allowed HTTP methods and headers. Only permit what is necessary for your application.
Preflight requests can introduce performance overhead. Use the Access-Control-Max-Age
header to cache the results of preflight requests for a specified duration, reducing the need for repeated preflight requests.
app.js
(continued)
const corsOptions = {
origin: 'http://example.com',
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400, // Cache preflight response for 24 hours
};
app.use(cors(corsOptions));
Monitor and log CORS requests to detect and investigate unauthorized access attempts or configuration issues. This can help you quickly identify and respond to potential security threats.
Let’s put everything together in a practical example. We’ll create an Express.js API that allows cross-origin requests only from a specific domain, handles credentials, and supports caching of preflight responses.
app.js
const express = require('express');
const cors = require('cors');
const app = express();
// CORS configuration
const corsOptions = {
origin: 'http://trustedwebsite.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // Allow credentials (cookies, authentication)
maxAge: 86400, // Cache preflight response for 24 hours
};
app.use(cors(corsOptions));
// Sample route that requires CORS
app.get('/data', (req, res) => {
res.json({ message: 'This is a CORS-enabled response' });
});
// Starting the server
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
http://trustedwebsite.com
makes a GET request to /data
, the server responds with the data, and the browser allows the request due to the correct CORS headers.credentials: true
setting.In this chapter, we explored the intricacies of Cross-Origin Resource Sharing (CORS) in the context of Express.js. We started with a basic understanding of what CORS is and why it's necessary in modern web development. We then delved into implementing and configuring CORS using the cors middleware in Express.js, covering everything from simple configurations to advanced use cases. Happy coding !❤️