CORS: Cross-Origin Resource Sharing
CORS is a security feature that restricts web pages from making requests to a different domain.
Cross-Origin Resource Sharing
CORS (Cross-Origin Resource Sharing) is a browser security mechanism that controls whether a webpage can request resources from a different domain. Understanding CORS is essential for building APIs and modern web applications, and for diagnosing one of the most common errors frontend developers encounter.
What Is CORS
Browsers enforce a security rule called the Same-Origin Policy. This policy prevents JavaScript running on one domain from making requests to a different domain. Without it, a malicious website could silently make requests to your bank, your email provider, or any other service where you are logged in, using your existing session cookies.
CORS is a controlled relaxation of the Same-Origin Policy. It allows servers to explicitly declare which outside origins are permitted to access their resources. When a browser sees a cross-origin request, it checks whether the server has granted permission via CORS headers before allowing the response to reach the JavaScript code that requested it.
Two URLs are considered the same origin only if they share the same scheme, domain, and port. A difference in any one of those three parts makes the request cross-origin, regardless of how similar the URLs look.
| Origin A | Origin B | Same Origin? | Reason |
|---|---|---|---|
| https://example.com | https://example.com/tutorial | Yes | Same scheme, domain, and port. Path does not affect origin. |
| https://example.com | http://example.com | No | Different scheme (https vs http) |
| https://example.com | https://api.example.com | No | Different subdomain counts as a different origin |
| https://example.com | https://example.com:8080 | No | Different port number |
| https://example.com | https://example.org | No | Different domain entirely |
How CORS Works
When JavaScript makes a cross-origin request, the browser does not simply send it and let the server decide whether to respond. Instead, the browser itself acts as a gatekeeper. It checks the server's CORS headers and blocks the response from reaching JavaScript if the current origin is not permitted.
For requests that could cause side effects on the server, such as POST requests with a JSON body or requests with custom headers, the browser first sends a preflight request using the OPTIONS method. This lightweight request asks the server what it allows before committing to the actual request.
- JavaScript on your page tries to call an API on a different domain
- If the request is non-simple, the browser sends a preflight OPTIONS request to the API first
- The server responds to the preflight with CORS headers declaring what origins, methods, and headers it permits
- If the browser sees that the current origin is allowed, it sends the actual request
- The server processes the actual request and responds with the data plus CORS headers again
- If the origin is not allowed at any stage, the browser blocks the response and throws a CORS error in the console
Simple requests, which are GET or POST requests with only standard headers and a content type of application/x-www-form-urlencoded, multipart/form-data, or text/plain, skip the preflight step and go directly to the actual request. However, the browser still checks the CORS headers on the response before passing it to JavaScript.
CORS Response Headers
CORS is controlled entirely through HTTP response headers that the server includes in its replies. The browser reads these headers and uses them to decide whether to grant or block access to the response.
| Header | What It Controls | Example Value |
|---|---|---|
Access-Control-Allow-Origin | Which origin or origins are permitted to access the resource | https://example.com or * for all origins |
Access-Control-Allow-Methods | Which HTTP methods the browser is permitted to use | GET, POST, PUT, DELETE |
Access-Control-Allow-Headers | Which request headers the browser is allowed to send | Content-Type, Authorization |
Access-Control-Allow-Credentials | Whether cookies and authentication headers are included in cross-origin requests | true |
Access-Control-Max-Age | How long the browser can cache the preflight response before sending another | 86400 (24 hours) |
Access-Control-Expose-Headers | Which response headers JavaScript is allowed to read beyond the default safe list | X-Custom-Header |
Common CORS Scenarios
CORS errors are among the most frequently encountered issues in frontend development. Most of them follow one of a few common patterns, each with a straightforward fix on the server side.
- React app on localhost:3000 calling a Node API on localhost:5000: Different ports make these cross-origin even though both are on localhost. Add a CORS middleware package to the API server and configure it to allow the frontend origin during development.
- Frontend on example.com calling api.example.com: Different subdomains are cross-origin. The API must explicitly set
Access-Control-Allow-Origin: https://example.comor include it in a dynamic allowlist. - Any site consuming a public third-party API: The API operator must allow your origin or use
Access-Control-Allow-Origin: *. You cannot fix CORS errors on APIs you do not control from the client side. - Sending cookies or authentication headers cross-origin: You must set
Access-Control-Allow-Credentials: trueon the server and use the exact origin rather than a wildcard. The fetch call must also includecredentials: 'include'.
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "https://example.com");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
res.setHeader("Access-Control-Allow-Credentials", "true");
if (req.method === "OPTIONS") return res.sendStatus(204);
next();
});
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
What Triggers a Preflight Request
Not every cross-origin request triggers a preflight. The browser only sends a preflight OPTIONS request when the actual request meets certain conditions that make it potentially unsafe to send without prior permission.
| Condition | Triggers Preflight? |
|---|---|
| GET or HEAD with only simple headers | No |
POST with application/x-www-form-urlencoded or text/plain | No |
POST with application/json body | Yes |
| PUT, PATCH, or DELETE requests | Yes |
Any request with an Authorization header | Yes |
| Any request with a custom header | Yes |
Frequently Asked Questions
- Is CORS a server-side or browser-side issue?
CORS is enforced by the browser, not by the server or the network. The server's only role is to include the correct response headers declaring what it permits. Requests made directly from server-to-server code, command-line tools, or Postman are never subject to CORS restrictions because those tools do not enforce the Same-Origin Policy. Only browsers do. - Does a wildcard origin work with credentials?
No. If you setAccess-Control-Allow-Credentials: true, you must specify the exact allowed origin inAccess-Control-Allow-Originrather than using a wildcard. A wildcard combined with credentials is explicitly disallowed by the CORS specification, and browsers will block the request even if the server sends both headers together. - Why do I get a CORS error in the browser but not in Postman?
Postman does not enforce the Same-Origin Policy because it is not a browser. CORS is a browser security feature, not an HTTP protocol rule. The server is responding perfectly fine in both cases, but the browser inspects the CORS headers on the response and blocks your JavaScript from accessing it if the origin is not permitted. Postman applies no such restriction. - Can I fix a CORS error from the frontend without changing the server?
Generally no, if you do not control the server. The only reliable fix is having the server send the correctAccess-Control-Allow-Originheader. One workaround during development is to proxy requests through your own backend, which forwards them to the target API server-to-server without triggering browser CORS checks. Some frameworks like Create React App and Vite have built-in proxy configuration for this purpose. - What is the difference between a simple request and a preflighted request?
A simple request is one the browser considers low-risk enough to send directly without asking permission first. These are typically GET requests or basic POST requests with standard form content types. A preflighted request involves methods, headers, or content types that could cause data modifications, so the browser sends a preliminary OPTIONS request to confirm the server is willing to accept it before sending the real request.
Conclusion
CORS exists to protect users from malicious cross-origin JavaScript requests while still allowing legitimate cross-origin communication between trusted origins. For API developers, setting the correct CORS headers is a fundamental configuration task that determines whether your frontend can communicate with your backend at all. Understanding which requests trigger preflights, how credentials interact with origin headers, and why browsers enforce rules that other tools do not will save you significant debugging time. To build a complete picture, explore HTTP headers, REST APIs, and HTTPS alongside your understanding of CORS.
