CORS Policy
Background
So far we talked about request headers. But responses also have headers. CORS Policy is about the response's header. Before going forward to what is CORS policy, first look into this practical example.
Imagine you are building a web application let's say a simple contact form. Frontend is built using React and backend is built using Express. You want to send the data from the front end to the backend. You can do it using HTTP requests. But what if the front end and back end are running on different origins? For example, the front end is running on https://frontend.com and the back end is running on https://backend.com. In short, the front end and back end are running on different origins. Now, if you try to send the data from the front end to the back end using HTTP requests, it will not work. This is because of the CORS policy.
General Concepts
What is the origin?
"Origin" is a combination of a scheme (also known as the protocol, for example, HTTP or HTTPS), hostname, and port (if specified).
same-origin and cross-origin
Websites that have the combination of the same scheme, hostname, and port are considered same-origin. Everything else is considered cross-origin.
| Origin A | Origin B | Explanation of whether Origin A and B are "same-origin" or "cross-origin" |
|---|---|---|
| https://www.mercurysols.org:443 | https://example.com:443 | cross-origin: different domain |
| https://www.mercurysols.org:443 | https://mercurysols.org:443 | cross-origin: different subdomain |
| https://www.mercurysols.org:443 | https://docs.mercurysols.org:443 | cross-origin: different subdomain |
| https://www.mercurysols.org:443 | https://www.mercurysols.org:8080 | cross-origin: different port |
| https://www.mercurysols.org:443 | http://www.mercurysols.org:443 | cross-origin: different scheme |
| https://www.mercurysols.org:443 | https://www.mercurysols.org:443 | same-origin: exact match (same scheme, hostname, and port) |
| https://www.mercurysols.org:443 | https://www.mercurysols.org | same-origin: Implicit port 443 |
| https://www.mercurysols.org:443 | https://www.mercurysols.org:443/about | same-origin (pathname is different, but that doesn't matter) |
Now, let's look into the CORS policy.
What is CORS?
CORS stands for Cross-Origin Resource Sharing. CORS policy is a security mechanism that allows restricting the resources. It prevents the JavaScript code from producing or consuming the requests against different origins.
Who uses CORS?
CORS policies are used by web browsers to enforce security policies for cross-origin requests. Any website that makes requests to different origins than the one that served the website is subject to CORS policies.
This includes websites that use APIs from different domains, websites that load resources from CDNs or third-party services, and websites that embed content from other domains using iframes.
How do CORS work?
The CORS (Cross-Origin Resource Sharing) mechanism works by adding HTTP headers to a server's response that indicate whether or not cross-origin requests should be allowed.
Here is an overview of how the CORS mechanism works:
- The client (such as a web browser) sends a request to a server for a resource from a different origin.
- The server checks the Origin header in the request to determine whether or not to allow the request.
- If the server decides to allow the request, it includes specific CORS headers in its response, such as Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers, that indicate which origins are allowed to access the resource, which HTTP methods are allowed, and which headers are allowed in the request.
- The client checks the CORS headers in the response to determine whether or not to allow the response.
- If the client decides to allow the response, it processes the response and sends it to the requesting application.
If the server does not include the appropriate CORS headers in its response, the client will not allow the response and will throw a CORS error.
You may wonder why you don't get a CORS policy error on Postman, but you get it on the browser, this is because Postman does not check and enforce CORS policies. It is a browser security feature.
How to enable CORS in Express?
You can enable CORS in Express by using the cors package. First, install the package using the following command:
- npm
- Yarn
- pnpm
- Bun
npm install cors
yarn add cors
pnpm add cors
bun add cors
Then, import the package into your Express application and use it as a global middleware. I will also show you how to enable CORS for specific routes later.
import express from "express";
import cors from "cors";
const app = express();
// Enable CORS
app.use(cors());
// It will enable CORS for all the domains
//...
This will enable CORS for all the domains, HTTP methods, and headers. Let's see what possible restrictions we can do using CORS.
There could be a misconception that CORS blocks the request. But that is not true. CORS policy will not block the request, will just add some headers to the response, and the same body will be returned. It is up to the browser to block the request or not.
Even if the CORS policy is not enabled, the request will be sent to the server, and the server will return the response. But the browser will block the response and throw an error.
CORS Policy on Specific Routes
As I told you CORS is a middleware, and above I was applying it to all the routes at global. But you can also apply it to specific routes as well. For example, if you want to apply CORS to only the /api/users route, you can do it as follows:
//...
app.use(
"/api/users",
cors({
/* options */
}),
(req, res) => {
// API logic
}
);
//...
Possible restrictions
We can do restrictions to make requests to the server. We can do the following things using CORS:
Domain restriction
We can restrict the domains that can access our backend. For example, if we want to allow only https://frontend.com to access our backend, we can do it as follows:
//...
app.use(cors({ origin: "https://frontend.com" }));
//...
You can also allow multiple origins to access your backend. For example, if you want to allow https://frontend.com and https://another-frontend.com to access your backend, you can do it as follows:
//...
app.use(
cors({
origin: ["https://frontend.com", "https://another-frontend.com"],
})
);
//...
If you want to allow all the origins to access your backend, you can do it as follows:
//...
app.use(cors({ origin: "*" }));
//...
All the origins except https://frontend.com can access your backend as follows:
//...
app.use(
cors({
origin: function (origin, callback) {
// Replace 'frontend.com' with the domain you want to block
if (origin === "https://frontend.com") {
callback(new Error("Access from this domain is not allowed"));
} else {
callback(null, true);
}
},
})
);
//...
With the above configuration, any requests originating from the specified domain (https://frontend.com in the example) will receive an error stating that access is not allowed. You can customize the error message or response as needed.
It's important to understand that CORS policies are enforced by the client's web browser. Although the configuration above will prevent requests from the blocked domain in most cases, it's not foolproof. Sophisticated clients can still bypass this restriction. Server-side validations and security measures are also recommended to ensure proper access control.
Method restriction
We can restrict the HTTP methods that can access our backend. For example, if we want to allow only GET and POST methods to access our backend, we can do it as follows:
//...
app.use(
cors({
methods: ["GET", "POST"],
})
);
//...
If you want to allow all the HTTP methods to access your backend, you can do it as follows:
//...
app.use(
cors({
methods: "*",
})
);
//...
Header restriction
We can restrict the headers that can be used in the request. For example, if we want to allow only Content-Type and Authorization headers in the request, we can do it as follows:
//...
app.use(
cors({
allowedHeaders: ["Content-Type", "Authorization"],
})
);
//...
If you want to allow all the headers in the request, you can do it as follows:
//...
app.use(
cors({
allowedHeaders: "*",
})
);
//...
Credential restriction
We can restrict the credentials that can be used in the request. For example, if we want to allow only true credentials in the request, we can do it as follows:
//...
app.use(
cors({
credentials: true,
})
);
//...
credentials is set to true to allow credentials (e.g., cookies, HTTP authentication). Set it to false if you don't need to support credentials.
You can also use combinations of the above restrictions as follows:
//...
app.use(
cors({
origin: "https://frontend.com",
methods: ["GET", "POST"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true,
})
);
//...
Response Headers
The following is the response headers that are added by the CORS policy:
Access-Control-Allow-Origin- It specifies the origins that are allowed to access the resource. It can be a single origin, multiple origins, or*to allow all the origins.Access-Control-Allow-Methods- It specifies the HTTP methods that are allowed to access the resource. It can be a single method, multiple methods, or*to allow all the methods.Access-Control-Allow-Headers- It specifies the headers that are allowed to be used in the request. It can be a single header, multiple headers, or*to allow all the headers.Access-Control-Allow-Credentials- It specifies whether the response can be shared when the request's credentials mode is "include". It can betrueorfalse.
Access-Control-Allow-Origin: https://frontend.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
How to add Response Headers manually
You may wonder if CORS policy is just adding some headers to the response. So, can't we add those headers manually? Yes, you can add those headers manually as follows:
//...
// Instead of using cors middleware
// app.use(cors());
// Add headers manually
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "https://frontend.com");
res.setHeader("Access-Control-Allow-Methods", "GET, POST");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
res.setHeader("Access-Control-Allow-Credentials", true);
next();
});
//...
While it is technically possible to add response headers manually instead of using the CORS middleware, it is generally recommended to use the CORS middleware for handling CORS-related headers. Here are a few reasons why using the CORS middleware is preferred:
- Standardization: The CORS middleware provides a standardized and well-tested solution for handling CORS headers. It ensures that the appropriate CORS headers are added to the response in a consistent and compliant manner, following the CORS specification.
- Simplified Configuration: Simplifies the configuration process by providing options to specify allowed origins, methods, headers, and other CORS-related settings. It abstracts the complexity of handling CORS headers, making it easier to configure and maintain CORS policies.
- Security and Best Practices: Takes care of important security considerations related to CORS, such as handling preflight requests, validating request origins, and preventing cross-origin attacks. It follows best practices and helps ensure that CORS-related security measures are properly implemented.
- Compatibility and Browser Support: Designed to work seamlessly with different browsers and their specific implementations of the CORS mechanism. It handles browser-specific quirks and inconsistencies, ensuring consistent behavior across different client environments.
- Future Updates and Improvements: Allows you to leverage updates and improvements made to the middleware itself. The middleware can be updated independently, providing you with the latest fixes and enhancements related to CORS handling without requiring changes to your application code.
Overall, using the CORS middleware is recommended because it provides a standardized, secure, and easier way to handle CORS headers. It abstracts the complexity of CORS handling, promotes best practices, and ensures compatibility across different browsers.
We talked about preflight requests in the above section. Let's talk about it in detail.
What is a preflight request?
A preflight request is an HTTP request that is sent by the browser to check if the CORS policy allows the request. It is sent before the actual request and is used to determine whether the actual request is safe to send. It is an HTTP OPTIONS request that contains the Origin header and other CORS-related headers. The server responds to the preflight request with the appropriate CORS headers. If the CORS policy allows the request, the browser sends the actual request. Otherwise, the browser throws an error.
Now you may wonder if the CORS middleware handles the preflight request. Yes, the CORS middleware handles the preflight request and sends the appropriate CORS headers in the response. It also handles the actual request and sends the appropriate CORS headers in the response. So, you don't have to worry about handling the preflight request and the actual request. The CORS middleware takes care of it for you.
One last thing, Does the browser send a preflight request for all requests? No, the browser sends preflight requests only for requests that are not considered safe. A request is considered safe if it is a GET or HEAD request or if it uses only the following headers: Accept, Accept-Language, Content-Language, Content-Type (but only with a MIME type of application/x-www-form-urlencoded, multipart/form-data, or text/plain).
Conclusion
In this article, we have learned about CORS and how to handle CORS in Node.js. We have also learned about the CORS middleware and how to use it to handle CORS-related headers. We have also learned about the different options that can be used to configure the CORS middleware. Finally, we have learned about the benefits of using the CORS middleware instead of adding CORS headers manually.