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