CORS
Cross-Origin Resource Sharing (CORS) controls which web origins (scheme, host, port) are permitted to access your HTTP APIs from browsers. This guide explains CORS concepts, how it works in Kestrun, and recommended patterns.
Overview
- Purpose: Limit which front-end origins can call your API via browser
fetch()or XHR. - Origin: A triplet of
scheme://host:port(e.g.,http://localhost:5000). Matching is exact. - Scope: Enforced by browsers. Servers emit CORS headers; browsers decide whether to allow responses.
- Preflight: Browsers send an
OPTIONSrequest to check permissions for certain methods/headers before the actual request.
How CORS Works
When a browser makes a cross-origin request:
- If it’s a “simple” request (e.g.,
GETwith standard headers), the browser may skip preflight. - If it’s a non-simple request (e.g.,
POSTwithContent-Type: application/json), the browser sends anOPTIONSpreflight includingOrigin,Access-Control-Request-Method, andAccess-Control-Request-Headers. - The server must respond with appropriate CORS headers (e.g.,
Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers). The browser then enforces access.
Kestrun CORS Integration
Kestrun exposes CORS via both PowerShell cmdlets and C# APIs. Internally, a CorsPolicyDefined flag ensures the CORS middleware is activated when at least one policy is registered.
- Activation: CORS middleware is wired only after a policy is registered. This avoids emitting CORS headers unintentionally.
- Policies: Define one default policy and/or multiple named policies.
- Route binding: Assign a named policy to specific routes or operations.
PowerShell Patterns
- Define default policy:
New-KrCorsPolicyBuilder |
Set-KrCorsOrigin -Origins 'http://localhost:5000' |
Set-KrCorsMethod -Methods GET |
Set-KrCorsHeader -Any |
Add-KrCorsPolicy -Default
- Define named policy:
New-KrCorsPolicyBuilder |
Set-KrCorsOrigin -Origins 'https://app.example.com' |
Set-KrCorsMethod -Methods GET, POST, DELETE |
Set-KrCorsHeader -Any |
Add-KrCorsPolicy -Name 'TrustedApp'
- Bind policy to route (OpenAPI attribute on functions):
function Get-Items {
[OpenApiPath(HttpVerb = 'Get', Pattern = '/items', CorsPolicy = 'TrustedApp')]
param()
Write-KrJsonResponse -StatusCode 200 -InputObject @{ items = @() }
}
- Allow-all (use with caution):
# Allows any origin, any method, any header
# Recommended only for local development or highly controlled scenarios
New-KrCorsPolicyBuilder |
Set-KrCorsOrigin -Any |
Set-KrCorsMethod -Any |
Set-KrCorsHeader -Any |
Add-KrCorsPolicy -Default
Preflight max age (cache OPTIONS)
Reduce browser preflight (OPTIONS) frequency by declaring a max age. You can specify the duration directly via -Seconds, or pass a TimeSpan.
# 2 hours via seconds
New-KrCorsPolicyBuilder |
Set-KrCorsOrigin -Origins 'http://localhost:3000' |
Set-KrCorsMethod -Methods GET, POST, PUT |
Set-KrCorsHeader -Any |
Set-KrCorsPreflightMaxAge -Seconds 7200 |
Add-KrCorsPolicy -Name 'WithMaxAge'
# Equivalent using TimeSpan
New-KrCorsPolicyBuilder |
Set-KrCorsOrigin -Origins 'http://localhost:3000' |
Set-KrCorsMethod -Methods GET, POST, PUT |
Set-KrCorsHeader -Any |
Set-KrCorsPreflightMaxAge -MaxAge (New-TimeSpan -Hours 2) |
Add-KrCorsPolicy -Name 'WithMaxAge'
Browsers honor Access-Control-Max-Age but may cap or ignore large values.
Expose custom headers (browser access)
Browsers only expose a small set of safe response headers to JavaScript. To read application-specific headers from fetch()/XHR responses (e.g., pagination counts), add them to the expose list using Set-KrCorsExposedHeader.
New-KrCorsPolicyBuilder |
Set-KrCorsOrigin -Origins 'http://localhost:3000' |
Set-KrCorsMethod -Any |
Set-KrCorsHeader -Any |
Set-KrCorsExposedHeader -Headers 'X-Total-Count','X-Page-Number' |
Add-KrCorsPolicy -Name 'WithExposedHeaders'
This emits Access-Control-Expose-Headers: X-Total-Count, X-Page-Number so browser JavaScript can read those headers from responses.
C# Patterns
- Default allow-all (use with caution):
var host = new KestrunHost(new LoggerConfiguration().CreateLogger());
host.AddCorsDefaultPolicyAllowAll();
- Builder-based policy:
var host = new KestrunHost(new LoggerConfiguration().CreateLogger());
var builder = new Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder()
.WithOrigins("https://app.example.com")
.WithMethods("GET", "POST")
.AllowAnyHeader();
host.AddCorsPolicy("TrustedApp", builder);
- Action-based policy:
host.AddCorsPolicy("TrustedAdmin", b =>
{
b.WithOrigins("https://admin.example.com")
.WithMethods("GET", "POST", "DELETE")
.AllowAnyHeader()
.WithExposedHeaders("X-Total-Count", "X-Page-Number")
.SetPreflightMaxAge(TimeSpan.FromHours(2))
.AllowCredentials();
});
Configuration Guidance
- Exact origin matching:
http://localhost:5000is not equal tohttp://127.0.0.1:5000. Use the precise origin your browser sends. - Prefer named policies: Assign granular policies per route group; avoid global allow-all.
- Preflight support: Include required methods and headers; consider
Access-Control-Max-Agewhen appropriate. - Credentials: Only enable with specific origins; credentials are not allowed with wildcard
*origins. - Default policy: Acts as fallback for routes without explicit policy.
Common Pitfalls
- Missing middleware: No headers emitted if no policy is registered.
- Wrong origin: Mismatch between UI host and API CORS origins (e.g.,
localhostvs127.0.0.1). - Headers not allowed: Custom headers (e.g.,
Authorization) require explicit allow. - Method not permitted: Ensure the policy includes
POST,PUT,DELETEwhen needed.
Security Notes
- Avoid broad
AllowAllin production. - Scope policies to exact origins and necessary methods.
- Revisit policies when deploying behind proxies/CDNs where the browser-facing origin changes.
Examples
See the tutorial for a multi-policy setup with a browser test UI:
- CORS Middleware Tutorial
- Full script include:
docs/_includes/examples/pwsh/15.8-Cors-Multipolicy.ps1
References
- New-KrCorsPolicyBuilder
- Set-KrCorsOrigin
- Set-KrCorsMethod
- Set-KrCorsHeader
- Set-KrCorsExposedHeader
- Set-KrCorsPreflightMaxAge
- Add-KrCorsPolicy
- Add-KrMapRoute
External Resources
- MDN: HTTP access control (CORS)
- MDN: CORS preflight requests
- ASP.NET Core: Enable Cross-Origin Requests (CORS)
- W3C: Fetch Standard