Cross‑Origin Resource Sharing (CORS) is the browser mechanism that controls whether a web page from one origin can call an API at another origin. In .NET projects this shows up the moment your SPA (e.g., Vite/React on http://localhost:5173) talks to an ASP.NET Core API running on a different port. This guide explains correct, production‑ready CORS configuration in .NET 8, with copy‑paste snippets and pitfalls to avoid.

At a Glance

  • Use an allow‑list of exact origins in dev and prod.
  • Place app.UseCors(...) before endpoint mapping and typically before auth.
  • Do not combine AllowAnyOrigin() with AllowCredentials().
  • Use SetIsOriginAllowedToAllowWildcardSubdomains() for *.example.com scenarios.
  • Enable CORS logs during troubleshooting.
Preflight refresher: many cross‑origin requests trigger an automatic browser OPTIONS "preflight" to check methods/headers. Make sure your policy allows the method and headers you intend to use.

A safe dev baseline

var builder = WebApplication.CreateBuilder(args);

// 1) Register CORS policies
builder.Services.AddCors(options =>
{
    options.AddPolicy("SpaDev", policy => policy
        .WithOrigins("http://localhost:5173", "https://localhost:5173")
        .AllowAnyHeader()
        .AllowAnyMethod());
});

var app = builder.Build();

// 2) Apply CORS before endpoints/auth
app.UseCors("SpaDev");

app.MapControllers();
app.Run();
Tip: Origins must match scheme, host, and port exactly — https://localhost:5173 is different from http://localhost:5173.

Understanding the "open" snippet (and why it's risky)

// Example often seen during quick tests
app.UseCors(x => x
    // .WithOrigins("http://localhost:5173", "https://localhost:5173")
    .AllowAnyMethod()
    .AllowAnyHeader()
    .SetIsOriginAllowed(origin => true) // any origin
    .AllowAnyOrigin()                    // any origin (no credentials)
    // .AllowCredentials()               // âš  not valid together with AllowAnyOrigin()
);

Use an open policy only for short‑lived local testing or truly public APIs. If you need cookies or Authorization headers, switch to a concrete allow‑list via .WithOrigins(...) and .AllowCredentials().

Production pattern: configure via appsettings.json

var allowed = builder.Configuration
    .GetSection("Cors:AllowedOrigins")
    .Get<string[]>() ?? Array.Empty<string>();

builder.Services.AddCors(options =>
{
    options.AddPolicy("Default", p => p
        .WithOrigins(allowed)
        .AllowAnyHeader()
        .AllowAnyMethod()
        // Add .AllowCredentials() only if you use cookies or Authorization headers
    );
});

var app = builder.Build();
app.UseCors("Default");
app.MapControllers();
app.Run();
{
  "Cors": {
    "AllowedOrigins": [
      "https://app.example.com",
      "https://admin.example.com"
    ]
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.Cors": "Information",
      "Microsoft.EntityFrameworkCore": "Information"
    }
  }
}

Why enable CORS logs?

Setting "Microsoft.AspNetCore.Cors": "Information" helps you see why a request was allowed or blocked while iterating.

Wildcard subdomains (*.example.com)

ASP.NET Core supports wildcard subdomain matching via SetIsOriginAllowedToAllowWildcardSubdomains(). You still need to declare the wildcard origins explicitly with scheme(s) you support.

builder.Services.AddCors(options =>
{
    options.AddPolicy("WildcardSubdomains", policy => policy
        .WithOrigins(
            "https://*.example.com",
            "https://*.example.net",
            "http://*.example.com"   // add http if you need it
        )
        .SetIsOriginAllowedToAllowWildcardSubdomains()
        .AllowAnyHeader()
        .AllowAnyMethod()
        // .AllowCredentials(); // valid with explicit origins (not with AllowAnyOrigin)
    );
});

var app = builder.Build();
app.UseCors("WildcardSubdomains");
app.MapControllers();
app.Run();

Allow requests from any origin

Public APIs or quick local testing only.

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAll", policy => policy
        .AllowAnyOrigin()
        .AllowAnyHeader()
        .AllowAnyMethod());
});

var app = builder.Build();
app.UseCors("AllowAll");

(Advanced) Reflect the request Origin and allow credentials

Security warning: The CORS spec forbids using a literal * (i.e., AllowAnyOrigin()) together with credentials. If you must support "any" origin with credentials, the only way is to reflect the requesting origin (the server echoes the exact Origin), which is risky. Prefer explicit allow‑lists.
builder.Services.AddCors(options =>
{
    options.AddPolicy("ReflectAnyOriginWithCreds", policy => policy
        .SetIsOriginAllowed(_ => true) // reflect the incoming Origin (risky)
        .AllowAnyHeader()
        .AllowAnyMethod()
        .AllowCredentials());
});

var app = builder.Build();
app.UseCors("ReflectAnyOriginWithCreds");

Middleware order checklist

  • app.UseRouting()
  • app.UseCors(...)
  • app.UseAuthentication() (if any)
  • app.UseAuthorization()
  • app.MapControllers()

Troubleshooting

  • Exact origin (scheme + host + port) is in your allow‑list.
  • No trailing slash in WithOrigins("https://app.example.com").
  • Preflight OPTIONS allowed? (method + headers).
  • CORS logs show policy applied (Microsoft.AspNetCore.Cors at Information).
  • Remember: server‑to‑server calls don't need CORS — only browsers enforce it.

References & Further Reading

  • Microsoft Learn — Enable Cross‑Origin Requests (CORS) in ASP.NET Core: docs
  • API — SetIsOriginAllowedToAllowWildcardSubdomains: docs
  • API — SetIsOriginAllowed: docs
  • MDN Web Docs — CORS overview: guide, and Preflight request: definition
  • Discussion of reflect‑origin pattern (use with caution): example