How to setup CORS in .NET Web API

Introduction
"It works in Postman but the browser blocks it" is the single most common first encounter with CORS, and the confusing part is that nothing about the server or the request is actually broken - the browser is enforcing a same-origin policy on your behalf, and the API has to explicitly opt certain origins back in. This video sets up CORS correctly in an ASP.NET Core Web API and covers the one setting combination that trips up almost everyone.
🎬 Watch the full video here:
CORS is a browser thing, not a server thing
The API itself has no problem serving a request from any origin - CORS is the browser refusing to hand the response back to the calling JavaScript unless the server's response headers explicitly allow it. Postman and curl don't enforce it because they're not the thing CORS is protecting - a browser tab running someone else's script is.
A named policy, not AllowAnyOrigin everywhere
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowReactApp", policy =>
{
policy.WithOrigins("https://myapp.com")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
app.UseCors("AllowReactApp");
UseCors has to go in the pipeline before endpoint mapping and before any [Authorize]-protected routes are reached - CORS headers need to be present on the response even for a preflight OPTIONS request that never touches your actual endpoint logic.
The setting combination that silently breaks: credentials
The moment the front end sends cookies or uses credentials: 'include', AllowAnyOrigin() stops being a valid option entirely - the CORS spec explicitly forbids combining a wildcard origin with credentialed requests, and the browser will reject the response outright:
policy.WithOrigins("https://myapp.com")
.AllowCredentials(); // requires an explicit origin, not "*"
This is the error that reads like a mystery the first time - a request that worked fine without cookies suddenly fails the moment authentication is added, and the fix is naming the origin explicitly rather than loosening the policy further.
Preflight requests: the extra round trip you didn't ask for
Any request with a method other than GET/HEAD/POST-with-simple-content-type, or with custom headers (an Authorization header, for instance), triggers a preflight - the browser sends an OPTIONS request first, asking the server "would you actually allow this?" before sending the real request. ASP.NET Core's CORS middleware answers preflights automatically once a policy is configured; you never write an OPTIONS handler yourself.
Common pitfalls
AllowAnyOrigin()combined withAllowCredentials(). The spec forbids this combination; the browser rejects it.- Calling
UseCorsafterUseAuthorizationor after endpoint mapping. The policy needs to apply before the pipeline gets that far. - Assuming a CORS error means the server never received the request. Often it did, and even ran the handler - the browser just refused to expose the response to JavaScript.
- Using a wildcard origin in production "temporarily" to unblock development, and never tightening it before shipping.
- Forgetting the policy needs to match the origin exactly, including scheme and port -
http://localhost:3000andhttps://localhost:3000are different origins.
Key Takeaways
- CORS is enforced by the browser, not the server - tools like Postman and curl bypass it entirely, which is why "works in Postman, fails in the browser" is a CORS symptom, not a server bug.
- Define named policies with explicit origins rather than reaching for
AllowAnyOrigin()by default. AllowCredentials()requires an explicit origin - it cannot be combined with a wildcard.UseCorsmust run before endpoint mapping and authorization in the middleware pipeline.- Preflight
OPTIONSrequests are handled automatically by ASP.NET Core's CORS middleware once a policy exists.
Get the Full Source Code
The complete runnable solution - the named CORS policy, the credentialed-request setup, and a minimal React client exercising both - is available to Patreon supporters. If you want the exact policy configuration instead of rebuilding it from the walkthrough above, you can find it on Patreon.