Core API CORS Configuration
When a React, Angular, or any browser-based frontend application tries to call the BookStore API from a different domain or port, the browser blocks the request by default. This is a browser security rule called the Same-Origin Policy. CORS (Cross-Origin Resource Sharing) is the mechanism that tells the browser which outside origins the API trusts and allows.
What Is an Origin?
An origin is the combination of a protocol, domain, and port. Two URLs have the same origin only if all three match exactly:
| URL | Origin | Same as API (localhost:7001)? |
|---|---|---|
| https://localhost:7001/api/books | https://localhost:7001 | Yes (same origin) |
| http://localhost:3000 | http://localhost:3000 | No (different port) |
| https://bookstore-frontend.com | https://bookstore-frontend.com | No (different domain) |
| http://localhost:7001 | http://localhost:7001 | No (different protocol) |
What Happens Without CORS?
Browser App at http://localhost:3000
|
| GET https://localhost:7001/api/books
v
BookStore API responds...
Browser checks CORS headers:
No "Access-Control-Allow-Origin" header → BLOCKED!
Error in browser console:
"Access to fetch at 'https://localhost:7001/api/books' from origin
'http://localhost:3000' has been blocked by CORS policy."
Configuring CORS in Program.cs
CORS is configured in two steps: define a policy, then apply it.
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Step 1: Define CORS policies
builder.Services.AddCors(options =>
{
// Policy for development - allow React dev server
options.AddPolicy("DevelopmentPolicy", policy =>
{
policy.WithOrigins("http://localhost:3000", "http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod();
});
// Policy for production - allow only the real frontend domain
options.AddPolicy("ProductionPolicy", policy =>
{
policy.WithOrigins("https://bookstore-frontend.com")
.WithHeaders("Authorization", "Content-Type")
.WithMethods("GET", "POST", "PUT", "DELETE");
});
});
var app = builder.Build();
// Step 2: Apply the CORS policy (must be BEFORE UseAuthorization)
if (app.Environment.IsDevelopment())
{
app.UseCors("DevelopmentPolicy");
}
else
{
app.UseCors("ProductionPolicy");
}
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
CORS Policy Options
| Method | What It Allows | Example |
|---|---|---|
WithOrigins() | Specific domains | .WithOrigins("https://myapp.com") |
AllowAnyOrigin() | Any domain (use with caution) | .AllowAnyOrigin() |
WithHeaders() | Specific request headers | .WithHeaders("Authorization") |
AllowAnyHeader() | Any header | .AllowAnyHeader() |
WithMethods() | Specific HTTP methods | .WithMethods("GET","POST") |
AllowAnyMethod() | All HTTP methods | .AllowAnyMethod() |
AllowCredentials() | Cookies and auth headers | .AllowCredentials() |
Environment-Based CORS Strategy
Development: Allow localhost:3000 (React), localhost:4200 (Angular)
Staging: Allow staging.bookstore-frontend.com
Production: Allow only bookstore-frontend.com (exact domain)
Store origins in appsettings.json to avoid hardcoding:
// appsettings.json
{
"Cors": {
"AllowedOrigins": [ "https://bookstore-frontend.com" ]
}
}
// appsettings.Development.json
{
"Cors": {
"AllowedOrigins": [ "http://localhost:3000", "http://localhost:4200" ]
}
}
// Program.cs — reading from config
var allowedOrigins = builder.Configuration
.GetSection("Cors:AllowedOrigins")
.Get<string[]>() ?? Array.Empty<string>();
builder.Services.AddCors(options =>
{
options.AddPolicy("BookStorePolicy", policy =>
{
policy.WithOrigins(allowedOrigins)
.AllowAnyHeader()
.AllowAnyMethod();
});
});
Applying CORS Per Controller or Action
Instead of applying CORS globally, it can be applied to specific controllers or actions using [EnableCors] and [DisableCors]:
[ApiController]
[Route("api/[controller]")]
[EnableCors("DevelopmentPolicy")] // ← only this controller allows cross-origin
public class BooksController : ControllerBase
{
[HttpGet]
[EnableCors("ProductionPolicy")] // ← override: this action uses a different policy
public async Task<IActionResult> GetAll() { ... }
[HttpDelete("{id:int}")]
[DisableCors] // ← cross-origin DELETE is blocked entirely
public async Task<IActionResult> Delete(int id) { ... }
}
The Preflight Request (OPTIONS)
Before sending a POST, PUT, or DELETE request with custom headers, the browser sends a preliminary preflight request using the HTTP OPTIONS method. This checks whether the API allows the actual request.
Browser sends preflight:
OPTIONS /api/books
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
API responds:
HTTP 204 No Content
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Browser confirms CORS is allowed → sends actual POST request
ASP.NET Core handles preflight requests automatically when CORS is configured. No extra code is needed.
Common CORS Mistakes
| Mistake | Problem | Fix |
|---|---|---|
Using AllowAnyOrigin() with AllowCredentials() | Not allowed — browser blocks it | Specify exact origins with WithOrigins() |
Registering UseCors() after UseAuthorization() | CORS headers not added to responses | Always place UseCors() before routing middleware |
| Trailing slash in origin | "https://myapp.com/" ≠ "https://myapp.com" | Remove trailing slashes from origin URLs |
CORS Middleware Order
// CORRECT order in Program.cs
app.UseMiddleware<ExceptionHandlingMiddleware>();
app.UseHttpsRedirection();
app.UseCors("BookStorePolicy"); // ← BEFORE UseAuthentication
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
Key Points
- CORS is a browser security feature — it only affects browser-based clients (not Postman or server-to-server calls).
- A CORS policy defines which origins, headers, and HTTP methods are allowed for cross-origin requests.
- Use
WithOrigins()with specific domains in production — never useAllowAnyOrigin()in production with authentication. - Store allowed origins in
appsettings.jsonso they can differ between Development, Staging, and Production. UseCors()must be placed beforeUseAuthentication()andUseAuthorization()in the middleware pipeline.
