CORS Configuration — Allowing Angular to Call the ASP.NET Core API

CORS (Cross-Origin Resource Sharing) is a browser security mechanism that blocks web pages from making HTTP requests to a different origin (protocol + domain + port) than the one that served the page. When Angular (served from localhost:4200) calls the ASP.NET Core API (running on localhost:5000), the browser blocks the request unless the API explicitly permits it via CORS headers. Configuring CORS correctly — permissive in development, locked-down in production — is the first step in connecting the frontend to the backend.

CORS Configuration in ASP.NET Core

// ── Program.cs — CORS setup ───────────────────────────────────────────────
var builder = WebApplication.CreateBuilder(args);

// ── Development policy — permissive ──────────────────────────────────────
builder.Services.AddCors(opts =>
{
    opts.AddPolicy("DevelopmentPolicy", policy =>
        policy
            .WithOrigins("http://localhost:4200", "https://localhost:4200")
            .AllowAnyMethod()         // GET, POST, PUT, DELETE, OPTIONS
            .AllowAnyHeader()         // Authorization, Content-Type, etc.
            .AllowCredentials());     // required for cookies (httpOnly refresh tokens)

    // ── Production policy — locked down ───────────────────────────────────
    opts.AddPolicy("ProductionPolicy", policy =>
        policy
            .WithOrigins(
                builder.Configuration["Cors:AllowedOrigins"]
                       .Split(',', StringSplitOptions.TrimEntries)
            )
            .WithMethods("GET", "POST", "PUT", "DELETE", "PATCH")
            .WithHeaders(
                "Authorization", "Content-Type", "X-Requested-With",
                "X-Idempotency-Key", "If-Match")
            .AllowCredentials()
            .SetPreflightMaxAge(TimeSpan.FromMinutes(10)));
                                      // cache preflight for 10 minutes
});

var app = builder.Build();

// Apply the appropriate policy based on environment:
var corsPolicy = app.Environment.IsDevelopment()
    ? "DevelopmentPolicy"
    : "ProductionPolicy";

app.UseCors(corsPolicy);  // MUST be before UseAuthentication and UseAuthorization

// appsettings.Production.json:
// { "Cors": { "AllowedOrigins": "https://blogapp.com,https://www.blogapp.com" } }

How CORS Preflight Works

-- ── CORS preflight sequence ───────────────────────────────────────────────
-- 1. Angular sends POST /api/posts with Authorization header
-- 2. Browser intercepts — different origin detected
-- 3. Browser sends OPTIONS /api/posts (preflight) with:
--    Origin: http://localhost:4200
--    Access-Control-Request-Method: POST
--    Access-Control-Request-Headers: Authorization, Content-Type
-- 4. ASP.NET Core responds with:
--    Access-Control-Allow-Origin: http://localhost:4200
--    Access-Control-Allow-Methods: POST
--    Access-Control-Allow-Headers: Authorization, Content-Type
--    Access-Control-Allow-Credentials: true
--    Access-Control-Max-Age: 600
-- 5. Browser sends the actual POST request
-- 6. Server responds with the data

-- Common CORS error in browser console:
-- "Access to XMLHttpRequest at 'http://localhost:5000/api/posts'
--  from origin 'http://localhost:4200' has been blocked by CORS policy:
--  No 'Access-Control-Allow-Origin' header is present on the requested resource."
-- → UseCors() not called, or called after UseRouting(), or wrong policy applied
Note: UseCors() must be placed in the middleware pipeline after UseRouting() but before UseAuthentication() and UseAuthorization(). If CORS middleware runs after authentication, a failed auth check returns a 401 without CORS headers — the browser sees this as a CORS error rather than an auth error, making debugging confusing. The correct order: UseRouting() → UseCors() → UseAuthentication() → UseAuthorization() → MapControllers().
Tip: For development, use the Angular proxy (proxy.conf.json) instead of CORS — the Angular dev server routes /api requests to the ASP.NET Core server on the same origin, so no CORS headers are needed at all. CORS only needs to be configured for deployed environments where the frontend and backend are on different domains. The proxy approach (Lesson 3) simplifies local development by eliminating the CORS configuration entirely during the development phase.
Warning: Never use AllowAnyOrigin() with AllowCredentials() — ASP.NET Core will throw at startup because browsers reject Access-Control-Allow-Origin: * with credentialed requests. You must specify explicit origins when using AllowCredentials(). Also never use a wildcard origin (*) in production — it allows any website to make authenticated requests to your API using the user’s cookies. Always explicitly list the allowed origins for production deployments.

Common Mistakes

Mistake 1 — UseCors() called after UseAuthentication() (CORS errors on 401 responses)

❌ Wrong — auth middleware runs first, returns 401 without CORS headers; browser sees CORS error instead of 401.

✅ Correct — UseRouting() → UseCors() → UseAuthentication() → UseAuthorization().

Mistake 2 — AllowAnyOrigin() with AllowCredentials() (startup exception)

❌ Wrong — .AllowAnyOrigin().AllowCredentials(); ASP.NET Core throws InvalidOperationException at startup.

✅ Correct — always use WithOrigins("https://specific-origin.com") when AllowCredentials() is needed.

🧠 Test Yourself

Angular sends a POST request with an Authorization: Bearer token header. The browser first sends an OPTIONS preflight. Why does the preflight not include the Authorization header?