Implement CORS in .NET Core the Production-Way


Cross-Origin Resource Sharing (CORS) is one of the most commonly misconfigured features in modern APIs. Many developers enable AllowAnyOrigin() during development and accidentally ship it to production — creating unnecessary security risks.

In this article, you'll learn how to implement CORS properly in ASP.NET Core for real-world production environments.

We’ll cover:

Official documentation:
ASP.NET Core CORS Documentation

What is CORS?

CORS (Cross-Origin Resource Sharing) is a browser security mechanism that controls how resources are requested from different domains.

Example:

Frontend:

https://app.example.com

Backend API:

https://api.example.com

Since the origins are different, the browser enforces CORS rules.

Without proper configuration, requests fail with errors like:

Access to fetch at 'https://api.example.com'
from origin 'https://app.example.com'
has been blocked by CORS policy

Understanding Origin

An origin consists of:

Scheme + Host + Port

Examples:

URL Origin
https://example.com Different
http://example.com Different
https://api.example.com Different
https://example.com:5001 Different

Even a port change creates a different origin.

Why CORS Exists

CORS protects users from malicious websites making unauthorized requests using browser credentials.

Without CORS, a malicious site could:

  • Read sensitive API responses
  • Steal authenticated data
  • Abuse user sessions

CORS is enforced by browsers, not by servers.

The Biggest Production Mistake

Many tutorials show this:

builder.Services.AddCors(options =>
{
    options.AddPolicy("OpenPolicy", policy =>
    {
        policy
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader();
    });
});
  • This is acceptable only for local development.
  • Never use unrestricted CORS in production for authenticated APIs.

Production-Ready CORS Configuration

The correct approach is:

  • Explicitly allow trusted origins
  • Restrict methods
  • Restrict headers
  • Handle credentials securely
  • Separate environments

Step 1 — Configure Allowed Origins

appsettings.json

{
  "Cors": {
    "AllowedOrigins": [
      "https://app.example.com",
      "https://admin.example.com"
    ]
  }
}

This keeps configuration centralized and environment-friendly.

Step 2 — Register CORS Policy

Program.cs (.NET 6+)

// Create builder
var builder = WebApplication.CreateBuilder(args);

// Read allowed origins from configuration
var allowedOrigins = builder.Configuration
    .GetSection("Cors:AllowedOrigins")
    .Get<string[]>();

// Register CORS
builder.Services.AddCors(options =>
{
    options.AddPolicy("ProductionCorsPolicy", policy =>
    {
        policy
            // Allow only trusted domains
            .WithOrigins(allowedOrigins)

            // Restrict HTTP methods
            .WithMethods("GET", "POST", "PUT", "DELETE")

            // Restrict headers
            .WithHeaders("Content-Type", "Authorization")

            // Allow cookies/auth tokens if needed
            .AllowCredentials();
    });
});

// Add controllers
builder.Services.AddControllers();

var app = builder.Build();

// Use CORS before authentication/authorization
app.UseCors("ProductionCorsPolicy");

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

Why AllowCredentials() Matters

If your frontend sends:

  • Cookies
  • JWT bearer tokens
  • Session authentication
  • Then credentials handling becomes important.

Example frontend request:

fetch("https://api.example.com/data", {
    credentials: "include"
});

When credentials are enabled:

  • AllowAnyOrigin() is forbidden
  • Origins must be explicit
  • This is intentional for security reasons.

Correct Middleware Order

Middleware order matters in ASP.NET Core.

Correct:

app.UseCors();

app.UseAuthentication();

app.UseAuthorization();

Incorrect ordering can cause:

  • Failed preflight requests
  • Missing headers
  • Authentication issues

Environment-Based CORS

Production and development usually require different rules.

appsettings.Development.json

{
  "Cors": {
    "AllowedOrigins": [
      "http://localhost:3000",
      "https://localhost:5173"
    ]
  }
}

appsettings.Production.json

{
  "Cors": {
    "AllowedOrigins": [
      "https://app.example.com"
    ]
  }
}

This prevents accidentally exposing development origins in production.

Supporting Multiple Frontends

Production APIs often support:

Use explicit origin lists:

policy.WithOrigins(
    "https://app.example.com",
    "https://admin.example.com",
    "https://partner.example.com"
);

Restrict Headers Properly

Avoid:

.AllowAnyHeader()

Prefer:

.WithHeaders(
    "Content-Type",
    "Authorization",
    "X-Requested-With"
)

This reduces attack surface and keeps policies predictable.

Restrict Methods Properly

Avoid:

.AllowAnyMethod()

Prefer:

.WithMethods("GET", "POST")

Only expose what your API truly supports.

Handling Preflight Requests

Browsers send an OPTIONS request before certain cross-origin calls.

This is called a preflight request.

Example:

OPTIONS /api/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST

ASP.NET Core handles this automatically when CORS is configured correctly.

Common Production Mistakes

1. Using AllowAnyOrigin()

Unsafe for authenticated APIs.

2. Mixing Credentials with Wildcards

Invalid:

.AllowAnyOrigin()
.AllowCredentials()

ASP.NET Core will reject this configuration.

3. Applying CORS Too Late

Wrong middleware order causes failures.

4. Forgetting Reverse Proxy Headers

When using:

  • Nginx
  • IIS
  • Azure App Gateway
  • Cloudflare

Ensure proxy configuration does not strip CORS headers.

Applying CORS to Specific Controllers

Instead of globally enabling CORS:

[EnableCors("ProductionCorsPolicy")]
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
}

Namespace required:

using Microsoft.AspNetCore.Cors;

This is useful for:

  • Public APIs
  • Multi-tenant systems
  • Admin-only endpoints

Dynamic CORS for Multi-Tenant SaaS

Some SaaS applications need dynamic origins.

Example:

policy.SetIsOriginAllowed(origin =>
{
    return origin.EndsWith(".example.com");
});

Use carefully.

Validate origins properly to avoid subdomain abuse.

Production Security Recommendations

Use HTTPS Only

Always allow secure origins:

https://app.example.com

Never allow plain HTTP in production.

Log Rejected Origins

Track suspicious cross-origin attempts.

Example use cases:

  • Threat monitoring
  • Misconfigured clients
  • Abuse detection

Keep Origins Configurable

Avoid hardcoding origins directly into source code.

Use:

  • Configuration files
  • Environment variables
  • Secret managers

Separate Public and Internal APIs

Internal admin APIs should not share permissive CORS rules with public APIs.

Example Enterprise Setup

builder.Services.AddCors(options =>
{
    options.AddPolicy("EnterprisePolicy", policy =>
    {
        policy
            .WithOrigins(
                "https://app.example.com",
                "https://admin.example.com"
            )
            .WithMethods("GET", "POST", "PUT")
            .WithHeaders(
                "Authorization",
                "Content-Type"
            )
            .AllowCredentials();
    });
});

This configuration is:

  • Explicit
  • Secure
  • Maintainable
  • Production-friendly

Testing CORS

Useful tools:

  • Browser DevTools
  • Postman
  • curl
  • Network tab inspection

Example curl request:

curl -H "Origin: https://app.example.com" \
     --verbose \
     https://api.example.com/users

Check response headers:

Access-Control-Allow-Origin: https://app.example.com

Final Thoughts

CORS is not just a frontend issue — it is a critical API security layer.

A production-ready CORS strategy should:

  • Explicitly trust origins
  • Avoid wildcards
  • Restrict methods and headers
  • Use environment-specific configuration
  • Support secure credential handling
  • Follow correct middleware order

If configured properly, CORS becomes predictable, secure, and easy to maintain even in large enterprise systems.

Further reading:

ASP.NET Core Security Overview

MDN CORS Guide

Microsoft ASP.NET Core GitHub Repository

0 Comments Report