By default, web browsers fiercely protect users. If your backend API is hosted at https://api.mycompany.com, and your frontend React app is hosted at https://react.mycompany.com, the browser will block the React app from reading the API's response. This is called the Same-Origin Policy. CORS is the mechanism we configure on the API to explicitly tell the user's browser: "It is safe to let that specific React app read my data."
CORS must be registered as a Service and then activated securely in the Middleware pipeline. It requires defining a "Policy".
var builder = WebApplication.CreateBuilder(args);
// 1. Define the specific CORS rules (The Policy)
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificFrontend", builder =>
{
builder.WithOrigins("https://react.mycompany.com", "http://localhost:3000") // Exactly who is allowed
.AllowAnyHeader() // Allowed to send JWTs, custom headers
.AllowAnyMethod(); // Allowed to use GET, POST, DELETE, PUT
});
});
var app = builder.Build();
// 2. Activates the Policy. MUST BE PLACED BEFORE MapControllers()!
app.UseCors("AllowSpecificFrontend");
app.MapControllers();
Sometimes you don't want global CORS. You might have a public endpoint that any website can access (like an embeddable weather widget) and private administrative endpoints.
[ApiController]
[Route("api/weather")]
// Applies the strict policy
[EnableCors("AllowSpecificFrontend")]
public class WeatherController : ControllerBase
{
[HttpGet]
public IActionResult GetWeather() { ... }
}
[ApiController]
[Route("api/public-widget")]
// Anyone on the internet can embed this
[EnableCors("AllowAllOriginsPolicy")]
public class WidgetController : ControllerBase
{ ... }
A fatal security flaw that juniors often commit is combining AllowAnyOrigin() with AllowCredentials(). If you do this, ASP.NET Core will physically crash on startup to protect you.
builder.WithOrigins("*") // ANYONE on the internet
.AllowCredentials(); // Is allowed to send cookie-based auth tokens
// Result: Cross-Site Request Forgery (CSRF). A malicious website can force
// the user's browser to send authenticated requests to your API.
Q: "When an Angular app makes an authenticated POST request to our Web API, the Network Tab shows two requests being made. The first is an HTTP OPTIONS request, and the second is the actual POST. Why did the browser send an OPTIONS request?"
Architect Answer: "That is called a CORS Preflight Request. Because a POST request combined with an Authorization header is considered a 'Complex Request', the browser must protect the server. Before the browser sends the physical POST payload, it sends an invisible HTTP OPTIONS request to the server asking: 'Is the domain angular.com allowed to send a POST request with an Authorization header to this specific URL?'. If your backend ASP.NET Core CORS middleware replies 'Yes', the browser then fires the actual POST request. If you haven't configured CORS properly on the backend, the Preflight fails, and the browser throws a generic CORS Error in the console."