# No 'Access-Control-Allow-Origin' Header Is Present in ASP.NET Core: Causes and Fixes

The browser console message is always some version of this:

```plaintext
Access to fetch at 'https://api.example.com/orders' from origin 'https://app.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
```

Getting "No 'Access-Control-Allow-Origin' header is present" from an ASP.NET Core API is one of those errors where the obvious fix - add CORS - is usually already done. The request works in Postman, it works in curl, the policy is right there in `Program.cs`, and the browser still refuses it. In production I've debugged this a dozen times, and the cause was rarely a missing policy. It was pipeline order, a trailing slash, or an exception that stripped the headers on the way out.

The diagnostic path below goes in the order that finds the cause fastest. The complete working configuration, including credentialed cross-origin auth and the middleware ordering tests that keep it correct, is on [Patreon](https://www.patreon.com/CodingDroplets).

## What the Error Actually Means

[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) is enforced entirely by the browser, not by your server. Your API returned a perfectly valid response; the browser looked for an `Access-Control-Allow-Origin` header that permits the calling page's origin, did not find one, and refused to hand the response to JavaScript.

Two consequences follow, and both matter for debugging:

*   **Postman and curl will never reproduce this.** They do not enforce CORS. A working curl proves nothing about the browser path.
    
*   **The server usually thinks it succeeded.** Your logs show a 200. The failure exists only in the browser.
    

So the real question is never "why did the request fail" - it is "why did the response come back without that header".

## Cause 1: Middleware Order

The most common cause by a wide margin. `UseCors` has to sit after `UseRouting` and before `UseAuthorization` and your endpoint mapping:

```csharp
app.UseRouting();
app.UseCors("Frontend");        // after routing, before auth
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
```

Put `UseCors` after `UseAuthorization` and the CORS headers are added too late for rejected requests. Put it before `UseRouting` and endpoint-specific policies never resolve. Either way the symptom is identical to having no CORS at all. Our [middleware pipeline checklist for .NET teams](https://codingdroplets.com/aspnet-core-middleware-pipeline-checklist-dotnet-teams) covers the wider ordering rules that this one belongs to.

Also check that nothing short-circuits ahead of `UseCors`. A custom middleware that returns early - a maintenance-mode gate, an API key check, a legacy rewrite - produces a response that never reaches the CORS middleware.

## Cause 2: The Origin Does Not Match Exactly

Origins are compared as exact strings across scheme, host, and port. These are all different origins, and only the exact one works:

| Configured | Browser sends | Match? |
| --- | --- | --- |
| `https://app.example.com` | `https://app.example.com` | Yes |
| `https://app.example.com/` | `https://app.example.com` | **No** - trailing slash |
| `http://app.example.com` | `https://app.example.com` | No - scheme |
| `https://app.example.com` | `https://app.example.com:8443` | No - port |
| `https://example.com` | `https://app.example.com` | No - subdomain |

The trailing slash is the one that costs people hours, because the string looks correct at a glance. `WithOrigins("https://app.example.com")` - no slash, ever.

For local development, remember that `http://localhost:3000` and `http://127.0.0.1:3000` are distinct origins even though they reach the same server.

## Cause 3: AllowAnyOrigin Combined With Credentials

If your frontend sends cookies or an `Authorization` header with `credentials: 'include'`, this configuration cannot work:

```csharp
// Invalid combination - wildcard origin with credentials
policy.AllowAnyOrigin().AllowAnyHeader().AllowCredentials();
```

The CORS specification forbids returning `Access-Control-Allow-Origin: *` alongside `Access-Control-Allow-Credentials: true`, and ASP.NET Core will not let you configure it. The fix is to name the origins explicitly, or to compute them:

```csharp
policy.WithOrigins("https://app.example.com")
      .AllowAnyHeader()
      .AllowCredentials();
```

When origins are dynamic - per-tenant subdomains, preview deployments - use `SetIsOriginAllowed` with a predicate that validates against an allow-list. Do not write a predicate that returns `true` unconditionally; that is `AllowAnyOrigin` wearing a disguise, and it re-enables the credentialed cross-origin requests the specification was protecting you from.

## Cause 4: The Preflight Request Is Being Rejected

Any request that is not a simple GET or POST with a basic content type triggers a preflight: the browser sends `OPTIONS` first and only sends the real request if that succeeds. When preflight fails, the console reports the same missing-header message, but the actual failure happened on a request you never wrote.

Three things break preflight:

*   **Authentication runs first and returns 401.** Preflight requests carry no credentials by design. They must be allowed anonymously, which is what correct middleware ordering gives you.
    
*   **A custom header is not in the policy.** If the client sends `X-Correlation-Id` and the policy does not list it, preflight fails. `AllowAnyHeader()` during development, an explicit `WithHeaders(...)` list in production.
    
*   **The method is not allowed.** A `PATCH` or `DELETE` against a policy configured only for GET and POST fails preflight, which frequently gets misreported as a routing problem. Our guide to [405 Method Not Allowed in ASP.NET Core](https://codingdroplets.com/405-method-not-allowed-aspnet-core-causes-fixes) covers where the two overlap.
    

Check the Network tab for the `OPTIONS` request specifically. If it is missing or non-2xx, that is your answer.

## Cause 5: The Response Is Actually a 500

This is the trap that wastes the most time, and it is the one most CORS write-ups skip entirely.

When your API throws, the exception handling middleware produces the error response. If that middleware sits before `UseCors` in the pipeline - which it does, because exception handling belongs first - the error response goes out without CORS headers. The browser then reports a CORS failure, and the front-end team spends a day on CORS configuration while the actual bug is an unhandled `NullReferenceException` in a repository.

**How to tell them apart:** look at the status code in the Network tab rather than the console message. A 500 with a CORS error in the console is a 500, not a CORS problem. Fix the exception; the CORS error disappears with it.

The same applies to 404s from a mistyped route and 413s from an oversized body. If the console says CORS but the status is anything other than 200, chase the status.

## Cause 6: A Proxy Is Stripping or Duplicating the Header

In front of a reverse proxy, an ingress controller, or an API gateway, two things go wrong:

*   **The header is stripped.** Some proxy configurations filter response headers they do not recognise.
    
*   **The header is added twice.** If nginx adds `Access-Control-Allow-Origin` and your app adds it as well, the browser sees two values and rejects the response. The message is identical to having none.
    

Curl the endpoint from outside the proxy and count the headers:

```bash
curl -i -H "Origin: https://app.example.com" https://api.example.com/orders
```

Exactly one `Access-Control-Allow-Origin` line, exactly matching the origin you sent. Pick one layer to own CORS and remove it from the other.

## Why Can the Browser Not Read My Custom Response Header?

Because `Access-Control-Allow-Origin` only governs whether the response body is readable. Response headers stay hidden from JavaScript unless you list them explicitly:

```csharp
policy.WithOrigins("https://app.example.com")
      .WithExposedHeaders("X-Total-Count", "X-Correlation-Id");
```

This shows up as pagination counts or correlation ids being `null` on the client while clearly present in the Network tab. Microsoft's [CORS documentation](https://learn.microsoft.com/en-us/aspnet/core/security/cors) covers the full set of policy options.

## How to Stop It Recurring

*   **Own CORS in exactly one layer.** Application or proxy, never both.
    
*   **Keep origins in configuration, not in code.** Environment-specific origin lists that require a rebuild guarantee someone hardcodes a wildcard during an incident.
    
*   **Never ship** `AllowAnyOrigin` **to production.** It is a fine local default and a standing invitation in production. Our [CORS policy decision guide](https://codingdroplets.com/aspnet-core-cors-policy-enterprise-decision-guide) covers named versus default versus endpoint-level policies properly.
    
*   **Add one automated check that asserts the header.** A test that sends an `Origin` header and asserts the response carries a matching `Access-Control-Allow-Origin` catches every ordering regression, and ordering regressions are the ones that keep happening.
    
*   **Check the status code before you touch CORS config.** Make it the first question anyone asks.
    

## FAQ

### Why does my API work in Postman but fail with a CORS error in the browser?

Because CORS is enforced by browsers, not servers. Postman and curl issue the request and hand you the response regardless of CORS headers, so they cannot reproduce the failure. A successful curl only tells you the endpoint works; it says nothing about whether the browser will be permitted to read the response.

### Why does the CORS error appear even though I called UseCors?

Almost always ordering. `UseCors` must come after `UseRouting` and before `UseAuthentication`, `UseAuthorization`, and endpoint mapping. It also has to run before any middleware that short-circuits the pipeline, since a response produced upstream never reaches the CORS middleware to have headers added.

### How do I allow credentials with CORS in ASP.NET Core?

Name the origins explicitly with `WithOrigins(...)` and add `AllowCredentials()`. The specification forbids combining credentials with a wildcard origin, so `AllowAnyOrigin()` is not an option here. For dynamic origins such as per-tenant subdomains, use `SetIsOriginAllowed` with a predicate that checks a real allow-list rather than returning true.

### Why do I get a CORS error only for PUT, PATCH, or DELETE requests?

Those methods trigger a preflight `OPTIONS` request, and GET or POST with a simple content type often does not. So the policy gaps that only affect preflight - missing methods, unlisted custom headers, authentication running before CORS - surface exclusively on those verbs. Inspect the `OPTIONS` request in the Network tab rather than the request you wrote.

### Is a CORS error in the console always a CORS problem?

No, and this is the most valuable thing to internalise. Any error response produced before the CORS middleware runs, including 500s from exception handling and 404s from routing, goes out without CORS headers and is reported by the browser as a CORS failure. Read the HTTP status first: anything other than a success status means you are chasing the wrong bug.

### Should CORS be handled in ASP.NET Core or at the API gateway?

Either works, but only one at a time. Two layers both adding `Access-Control-Allow-Origin` produce a duplicated header that browsers reject with the same message as having none. Handling it in the application keeps the policy versioned with the code; handling it at the gateway centralises it across services. Pick one and remove the other.

* * *

## About the Author

I'm Celin Daniel, Co-founder of [Coding Droplets](https://codingdroplets.com/). I've been building .NET and ASP.NET Core systems in production for 13+ years - APIs, distributed backends, enterprise platforms. Everything I write here comes from real shipping experience: patterns that held up, trade-offs that bit us, and lessons learned the hard way.

*   GitHub: [codingdroplets](http://github.com/codingdroplets/)
    
*   YouTube: [Coding Droplets](https://www.youtube.com/@CodingDroplets)
    
*   Website: [codingdroplets.com](https://codingdroplets.com/)
