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:

URLOriginSame as API (localhost:7001)?
https://localhost:7001/api/bookshttps://localhost:7001Yes (same origin)
http://localhost:3000http://localhost:3000No (different port)
https://bookstore-frontend.comhttps://bookstore-frontend.comNo (different domain)
http://localhost:7001http://localhost:7001No (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

MethodWhat It AllowsExample
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

MistakeProblemFix
Using AllowAnyOrigin() with AllowCredentials()Not allowed — browser blocks itSpecify exact origins with WithOrigins()
Registering UseCors() after UseAuthorization()CORS headers not added to responsesAlways 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 use AllowAnyOrigin() in production with authentication.
  • Store allowed origins in appsettings.json so they can differ between Development, Staging, and Production.
  • UseCors() must be placed before UseAuthentication() and UseAuthorization() in the middleware pipeline.

Leave a Comment