When calling an API on a different domain via fetch from the frontend, the browser console turns red: "Access to fetch … has been blocked by CORS policy." When hosting frontend and backend separately in custom development, developers hit this wall almost without exception. Searching for help yields top results suggesting that "adding Access-Control-Allow-Origin: * fixes it." Pasting it does make the error disappear. But that may simply be the equivalent of setting a locked door to "let everyone in" just to stop it from blocking you.
Articles titled "Developers don't understand CORS" routinely trend on Hacker News, showing how widespread this state of "the error is gone, but the mechanism is misunderstood" truly is. When delivering APIs in custom development, leaving them set to "allow all for now" is out of the question. In this article, we explain the fundamentals of what CORS protects and map out how to turn risky shortcuts into secure configurations.
CORS is a mechanism to protect users, not servers
The primary source of confusion is the misconception that CORS is a security mechanism restricting access to servers. The reality is quite the opposite. CORS is a restriction enforced by the browser to protect users.
It is predicated on the browser's foundational rule: the same-origin policy. This prevents JavaScript running on one site (origin) from reading arbitrary resources from another origin—without it, simply visiting a malicious site could run code that reads private data from your online banking session open in another tab. CORS is the mechanism by which a server explicitly grants an exception to this strict rule, declaring that this specific origin is allowed to read responses.
In other words, the Access-Control-Allow-Origin header is a permission slip from the server informing the browser that requests from a specific origin are permitted to read the response. The error appears because the server has not granted permission to that origin. Eliminating this with * (allowing all origins) is equivalent to marking the permission slip as "anyone permitted." While the error disappears, it exposes everything you intended to protect.
Misunderstanding this derails API integration architecture from the ground up. This core concept is also intimately tied to how authentication credentials are handled. Looking at cookie and token management alongside the perspectives covered in our article on auditing JWT, cookie, and session designs provides a clear, multidimensional view of the relationship between CORS and authentication.
Why "allow all for now" is dangerous
Pasting Access-Control-Allow-Origin: * becomes exceptionally hazardous when dealing with APIs that handle credentials.
Under browser specifications, * (wildcard allow) and credentials support (credentials) cannot be used together. Consequently, when trying to permit all origins on an authenticated API, developers often resort to an even more dangerous workaround: echoing back the request origin directly into the allow header.
// やってはいけない例:来たオリジンを無検証でそのまま許可する
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", req.headers.origin); // 危険
res.header("Access-Control-Allow-Credentials", "true");
next();
});
This translates to "whatever origin requests access, allow that origin"—effectively permitting all origins while simultaneously allowing credentials to be transmitted. Malicious sites can now make authenticated requests and receive responses, completely neutralizing the protections CORS was designed to provide. Under the guise of "fixing the error," the safeguard protecting users has been dismantled.
Correct configurations to adopt in custom development
The proper approach is neither * nor origin echoing, but explicitly enumerating allowed origins via an allowlist.
| Common approach | Consequence | Evaluation in custom development |
|---|---|---|
Allow-Origin: * | Grants read access to all origins; credentials cannot be sent | Unacceptable except for public, unauthenticated APIs |
| Reflecting the incoming origin without validation | Effectively allows all origins while enabling credentials | Dangerous; must never be included in production deliverables |
| Validating against an allowlist before returning headers | Allows only registered origins | The baseline standard |
Implementation consists of verifying whether the request origin is present in a predefined allowlist, returning that origin in the allow header only when a match is confirmed.
const allowedOrigins = ["https://app.example.com", "https://admin.example.com"];
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.header("Access-Control-Allow-Origin", origin);
res.header("Access-Control-Allow-Credentials", "true");
}
next();
});
An operational best practice is managing the allowlist via environment variables across production, staging, and development rather than hardcoding values into application source. Furthermore, when using PUT, DELETE, or custom headers, the browser will issue a preflight OPTIONS request, requiring Access-Control-Allow-Methods and Access-Control-Allow-Headers to be returned accordingly. How misconfigurations at the API gateway layer create security loopholes also connects directly to cases covered in our article on how API Gateway misconfigurations cause authorization bypasses.
Real-world remediation in client projects
On a SaaS API where our company was engaged for a security review (keeping the company name confidential), the frontend and backend operated on different domains, and developers had resolved development CORS errors by simply echoing back the incoming origin. Because authentication relied on cookies and included Allow-Credentials: true, any website could query this API with active credentials. The team assumed CORS was properly configured, unaware that it was wide open.
We transitioned the setup to an allowlist model, configuring production, staging, and local origins through environment variables. We simply shifted from unconditionally permitting whatever arrived to permitting only verified origins. We also cleaned up preflight responses, paring down permissions for unneeded methods and headers. Visual functionality remained unchanged, but unauthorized, credentialed access from malicious sites was successfully blocked.
What proved most impactful in this project was sharing the understanding across the team that resolving a CORS error does not mean it was configured correctly. There are countless wrong ways to silence an error. Only after understanding what the mechanism protects can you judge whether a fix is sound. Understanding comes first; configuration follows.
Where to begin
The first step is checking how your APIs currently configure CORS. If Access-Control-Allow-Origin contains * or echoes incoming origins verbatim, it warrants immediate review. For credentialed APIs, switching to an explicit origin allowlist and managing those lists per environment restores security without disrupting functionality.
If you lack confidence in CORS configurations across separate domain setups, have live production systems running on wildcard permissions, or want to audit your API security posture, reach out via GleamHub's contact form. We will review your current CORS and authentication architecture to establish a secure setup without disrupting operations.








