Skip to content

codingdroplets/aspnet-core-cors-policy-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ASP.NET Core CORS Policy Demo

Named, Default & Endpoint-Level CORS configuration for ASP.NET Core Web API β€” with security best practices, preflight handling, and anti-pattern examples.

Visit CodingDroplets YouTube Patreon Buy Me a Coffee GitHub


πŸš€ Support the Channel β€” Join on Patreon

If this sample saved you time, consider joining our Patreon community. You'll get exclusive .NET tutorials, premium code samples, and early access to new content β€” all for the price of a coffee.

πŸ‘‰ Join CodingDroplets on Patreon

Prefer a one-time tip? Buy us a coffee β˜•


🎯 What You'll Learn

  • How to define named CORS policies (AddCors + options.AddPolicy) in ASP.NET Core
  • How to apply a global default policy via app.UseCors("PolicyName")
  • How to override per-controller or per-action with [EnableCors("PolicyName")]
  • How to opt out of CORS entirely with [DisableCors] (and when you'd ever want to)
  • The difference between AllowAnyOrigin() and WithOrigins(...) β€” and why it matters with credentials
  • How preflight OPTIONS requests are handled automatically by the middleware
  • Security best practices: principle of least privilege for API origins, headers, and methods
  • Common CORS anti-patterns and how to avoid them

πŸ—ΊοΈ Architecture Overview

Browser Request
      β”‚
      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            CORS Middleware              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Evaluate against active policy  β”‚   β”‚
β”‚  β”‚  (global default or [EnableCors])β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚  β€’ Preflight (OPTIONS) handled here     β”‚
β”‚  β€’ Access-Control-* headers added       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚
      β–Ό
  HTTP Router
      β”‚
      β–Ό
  Controller Action
      β”‚   [EnableCors("PublicApiPolicy")]   β†’ PublicApiPolicy
      β”‚   (no attribute β€” inherits default) β†’ FrontendPolicy
      β”‚   [EnableCors("AdminPolicy")]       β†’ AdminPolicy
      β”‚   [DisableCors]                     β†’ No CORS headers
      β”‚
      β–Ό
   Response
      β”‚
      β–Ό
Browser (respects Access-Control-Allow-Origin header)

πŸ“‹ Policy Summary

Policy Origins Credentials Headers Methods Use Case
PublicApiPolicy Any (*) ❌ Not allowed Any Any Public read-only endpoints
FrontendPolicy (default) app.example.com, localhost:3000 βœ… Allowed Any Any Authenticated SPA clients
AdminPolicy admin.example.com ❌ Not set Content-Type, Authorization GET, POST, PUT, DELETE Admin back-office (strict)

Key rule: AllowAnyOrigin() and AllowCredentials() are mutually exclusive. ASP.NET Core will throw at startup if you combine them β€” by design, since a wildcard origin with credentials is a security vulnerability.


πŸ“ Project Structure

aspnet-core-cors-policy-demo/
β”œβ”€β”€ aspnet-core-cors-policy-demo.sln
└── CorsDemo/
    β”œβ”€β”€ CorsDemo.csproj                 # Project file β€” Scalar.AspNetCore package
    β”œβ”€β”€ Program.cs                      # CORS policy definitions + middleware pipeline
    β”œβ”€β”€ appsettings.json                # App configuration
    β”œβ”€β”€ Controllers/
    β”‚   β”œβ”€β”€ PublicController.cs         # [EnableCors("PublicApiPolicy")] override
    β”‚   β”œβ”€β”€ ProductsController.cs       # Uses global FrontendPolicy (no attribute)
    β”‚   └── AdminController.cs          # [EnableCors("AdminPolicy")] + [DisableCors] demo
    └── Properties/
        └── launchSettings.json         # Scalar UI auto-launch on F5

πŸ› οΈ Prerequisites

Requirement Version
.NET SDK 10.0+
IDE Visual Studio 2022 v17.x / VS Code / Rider

⚑ Quick Start

# Clone the repository
git clone https://github.com/codingdroplets/aspnet-core-cors-policy-demo.git
cd aspnet-core-cors-policy-demo

# Restore and build
dotnet build -c Release

# Run the API
cd CorsDemo
dotnet run

Open your browser to http://localhost:5289/scalar/v1 β€” the Scalar API explorer opens automatically.


πŸ”§ How It Works

1. Define named CORS policies (Program.cs)

builder.Services.AddCors(options =>
{
    // Policy 1: Public endpoints β€” any origin, no credentials
    options.AddPolicy("PublicApiPolicy", policy =>
        policy
            .AllowAnyOrigin()
            .AllowAnyHeader()
            .AllowAnyMethod());

    // Policy 2: Authenticated SPA β€” explicit origins + credentials
    options.AddPolicy("FrontendPolicy", policy =>
        policy
            .WithOrigins("https://app.example.com", "http://localhost:3000")
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials());

    // Policy 3: Admin back-office β€” maximum restriction
    options.AddPolicy("AdminPolicy", policy =>
        policy
            .WithOrigins("https://admin.example.com")
            .WithHeaders("Content-Type", "Authorization")
            .WithMethods("GET", "POST", "PUT", "DELETE"));
});

2. Apply a global default policy

// Must come before UseAuthorization() and MapControllers()
app.UseCors("FrontendPolicy");

3. Override at controller level with [EnableCors]

[ApiController]
[Route("api/public")]
[EnableCors("PublicApiPolicy")] // overrides FrontendPolicy for this entire controller
public class PublicController : ControllerBase { ... }

4. Override at action level with [EnableCors] or [DisableCors]

[ApiController]
[Route("api/admin")]
[EnableCors("AdminPolicy")] // controller-wide override
public class AdminController : ControllerBase
{
    [HttpGet("health")]
    [DisableCors] // no CORS headers on this action β€” cross-origin blocked intentionally
    public IActionResult GetHealth() { ... }
}

5. Preflight handling

ASP.NET Core CORS middleware handles OPTIONS preflight requests automatically. No controller action is needed β€” the middleware intercepts and responds with the appropriate Access-Control-* headers before the request reaches your controller.


πŸ“‘ API Endpoints

Method Endpoint CORS Policy Description
GET /api/public PublicApiPolicy (any origin) Public announcements β€” no auth required
GET /api/products FrontendPolicy (default) List all products
GET /api/products/{id} FrontendPolicy (default) Get product by ID
POST /api/products FrontendPolicy (default) Create a new product
GET /api/admin AdminPolicy (admin.example.com only) List admin users
DELETE /api/admin/{id} AdminPolicy (admin.example.com only) Delete an admin user
GET /api/admin/health None [DisableCors] Health check β€” cross-origin blocked
GET /scalar/v1 N/A Scalar API explorer (development only)
GET /openapi/v1.json N/A OpenAPI JSON document

πŸ§ͺ Running Tests

This project is focused on demonstrating CORS middleware configuration. You can test all CORS behaviours directly via the Scalar UI or with curl:

# Test PublicApiPolicy β€” any origin works
curl -H "Origin: https://random.example.com" \
     -H "Access-Control-Request-Method: GET" \
     -X OPTIONS http://localhost:5289/api/public -v

# Test FrontendPolicy β€” allowed origin
curl -H "Origin: http://localhost:3000" \
     -H "Access-Control-Request-Method: GET" \
     -X OPTIONS http://localhost:5289/api/products -v

# Test AdminPolicy β€” disallowed origin (expect no CORS headers in response)
curl -H "Origin: https://attacker.com" \
     -H "Access-Control-Request-Method: GET" \
     -X OPTIONS http://localhost:5289/api/admin -v

# Test [DisableCors] β€” no CORS headers regardless of origin
curl -H "Origin: https://admin.example.com" \
     -H "Access-Control-Request-Method: GET" \
     -X OPTIONS http://localhost:5289/api/admin/health -v

πŸ€” Key Concepts

Named vs Default vs Endpoint-Level CORS

Approach How to Apply Scope Best For
Default policy app.UseCors("Name") Entire app Single-policy APIs
Named policy [EnableCors("Name")] Controller or action Mixed-policy APIs
Disable CORS [DisableCors] Specific action Internal diagnostic routes

AllowAnyOrigin vs WithOrigins

// βœ… Safe for public, unauthenticated data
policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()

// βœ… Required when credentials (cookies/auth headers) are involved
policy.WithOrigins("https://app.example.com").AllowCredentials()

// ❌ INVALID β€” throws InvalidOperationException at runtime
policy.AllowAnyOrigin().AllowCredentials()

Why UseCors() placement matters

app.UseRouting();
app.UseCors("MyPolicy");   // ← must be AFTER UseRouting, BEFORE UseAuthorization
app.UseAuthorization();
app.MapControllers();

CORS is browser-enforced, not server-enforced

CORS headers tell the browser whether to allow the response. A malicious server-to-server call can always ignore CORS. Use authentication and authorisation to secure your API β€” CORS alone is not a security boundary.


🏷️ Technologies Used

  • ASP.NET Core 10 β€” Web API framework
  • Microsoft.AspNetCore.Cors β€” Built-in CORS middleware (no extra package needed)
  • Scalar.AspNetCore β€” Beautiful OpenAPI/Scalar UI replacing Swagger UI
  • System.Text.Json β€” Built-in JSON serialisation with ReferenceHandler.IgnoreCycles
  • .NET 10 SDK β€” Target framework

πŸ“š References


πŸ“„ License

This project is licensed under the MIT License.


πŸ”— Connect with CodingDroplets

Platform Link
🌐 Website https://codingdroplets.com/
πŸ“Ί YouTube https://www.youtube.com/@CodingDroplets
🎁 Patreon https://www.patreon.com/CodingDroplets
β˜• Buy Me a Coffee https://buymeacoffee.com/codingdroplets
πŸ’» GitHub http://github.com/codingdroplets/

Want more samples like this? Support us on Patreon or buy us a coffee β˜• β€” every bit helps keep the content coming!

About

Demonstrates Named, Default, and Endpoint-Level CORS policies in ASP.NET Core Web API with security best practices, preflight handling, and anti-pattern examples.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages