CORS (Cross-Origin Resource Sharing)

15 questions found

What is CORS, and why does an ASP.NET Core API need to explicitly configure it for browser-based clients on a different origin?

Beginner
CORS is a browser-enforced security mechanism restricting web pages from making requests to a different origin (scheme, domain, or port) than the one that served the page, unless the target server explicitly permits it via response headers -- an ASP.NET Core API called from a frontend hosted on a different origin (like a React app on a different port during development) must explicitly configure a CORS policy or the browser will block the response from reaching the calling JavaScript.
builder.Services.AddCors(options => {
    options.AddPolicy("AllowFrontend", policy => {
        policy.WithOrigins("https://app.mycompany.com").AllowAnyMethod().AllowAnyHeader();
    });
});

app.UseCors("AllowFrontend");
Real-world example A single-page app hosted at app.mycompany.com calling an API at api.mycompany.com (different subdomain, hence different origin) needs the API to explicitly configure CORS to allow requests from app.mycompany.com's origin.

Common follow-ups: What exactly counts as a different 'origin' (scheme, host, port)?;Why is CORS enforced by the browser rather than being a server-side security boundary?

ASP.NET Core Middleware & Request Pipeline;Authentication

Where must UseCors be positioned in the middleware pipeline relative to UseRouting and UseAuthorization, and why does this order matter?

Intermediate
UseCors must come after UseRouting (since it may need endpoint-specific policy metadata from routing) but before UseAuthorization (since a CORS preflight OPTIONS request carries no authentication credentials and would otherwise be incorrectly rejected by authorization checks before CORS ever gets to respond) -- misordering this causes confusing failures where legitimate cross-origin requests to protected endpoints fail with CORS errors instead of proper authorization behavior.
app.UseRouting();
app.UseCors("MyPolicy");   // after routing, before authorization
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
Real-world example A production bug where cross-origin requests to an [Authorize]-protected endpoint always failed with CORS errors (not auth errors) was traced to UseCors being registered after UseAuthorization instead of before, causing preflight requests to be rejected by the authorization check first.

Common follow-ups: What HTTP status does a rejected preflight request typically show in browser dev tools?;How does this ordering interact with endpoint-specific [EnableCors] attributes?

ASP.NET Core Middleware & Request Pipeline;Authorization

What is a CORS preflight request, and when does the browser automatically send one before the actual request?

Advanced
For 'non-simple' requests (using methods other than GET/HEAD/POST, custom headers, or a Content-Type outside a few basic ones like application/x-www-form-urlencoded), the browser sends an OPTIONS preflight request first, asking the server whether it's willing to accept the actual cross-origin request via Access-Control-Request-Method and Access-Control-Request-Headers headers -- only if the server responds with matching Access-Control-Allow-* headers does the browser proceed to send the real request.
// Browser automatically sends BEFORE a PUT request with a custom header:
// OPTIONS /api/products/5
// Access-Control-Request-Method: PUT
// Access-Control-Request-Headers: X-Custom-Header

// Server must respond with matching Access-Control-Allow-Methods/Headers
Real-world example A team debugging why PUT requests with a custom Authorization-like header mysteriously fail cross-origin discovers the API never explicitly allowed that custom header in its CORS policy, causing the preflight OPTIONS request to fail silently before the real request is ever sent.

Common follow-ups: What HTTP methods/headers count as 'simple' and avoid triggering a preflight?;How can preflight results be cached client-side to avoid repeating them on every request?

RESTful Web APIs & Controllers;ASP.NET Core Middleware & Request Pipeline

Why can't AllowCredentials() be combined with a wildcard origin (AllowAnyOrigin), and what does this mean for cookie-authenticated cross-origin APIs?

Intermediate
The CORS specification explicitly forbids combining AllowCredentials (needed for cookie-based auth or client certificates across origins) with a wildcard origin, as a security measure -- if credentialed requests were allowed from any origin, it would defeat CORS's core purpose of restricting which sites can make authenticated requests on a user's behalf, so you must specify exact allowed origins when credentials are involved.
// INVALID combination -- throws at runtime
// policy.AllowAnyOrigin().AllowCredentials();

// Correct: specific origins required with credentials
policy.WithOrigins("https://app.mycompany.com")
      .AllowCredentials()
      .AllowAnyHeader().AllowAnyMethod();
Real-world example A team's cookie-based authentication mysteriously stopped working cross-origin after someone changed a CORS policy from specific origins to AllowAnyOrigin(), not realizing this silently disables credentialed requests due to the spec-level incompatibility.

Common follow-ups: Why is combining wildcard origins with credentials considered a security risk?;How do JWT bearer tokens sidestep this particular constraint compared to cookies?

Authentication;Authorization

How do you apply different CORS policies to different controllers or minimal API endpoint groups within the same application?

Advanced
Register multiple named policies via AddCors, then apply a specific one per controller/action using [EnableCors("PolicyName")] (overriding any default/global policy), or per minimal API endpoint/group using .RequireCors("PolicyName") -- use [DisableCors] to explicitly opt an endpoint out of CORS entirely regardless of the global configuration.
builder.Services.AddCors(options => {
    options.AddPolicy("PublicApi", p => p.AllowAnyOrigin().WithMethods("GET"));
    options.AddPolicy("AdminApi", p => p.WithOrigins("https://admin.mycompany.com").AllowCredentials());
});

[EnableCors("PublicApi")]
public class ProductsController : ControllerBase { }

app.MapGroup("/admin").RequireCors("AdminApi");
Real-world example A single API exposes both a fully public, read-only product catalog (permissive CORS for any origin) and a sensitive admin management interface (restricted to only the internal admin frontend's origin with credentials), using distinct named CORS policies.

Common follow-ups: What happens if both a global UseCors default policy and a per-endpoint policy are present?;How does [DisableCors] interact with a permissive global default?

Authentication;Controllers vs Minimal APIs

What security risk does dynamically reflecting the Origin header back as Access-Control-Allow-Origin introduce, and why is it a dangerous anti-pattern?

Intermediate
While AllowAnyOrigin combined with AllowCredentials is blocked by the spec, some developers work around it by dynamically 'reflecting' whatever Origin header the request sends back as the allowed origin value (via SetIsOriginAllowed(_ => true)), effectively allowing every origin while still technically satisfying the credentials requirement -- this completely defeats CORS's security purpose, allowing any malicious website to make authenticated cross-origin requests using a logged-in user's cookies.
// DANGEROUS anti-pattern: reflecting any origin defeats CORS's entire purpose
policy.SetIsOriginAllowed(origin => true).AllowCredentials();

// SAFE: explicit allowlist of known, trusted origins only
policy.WithOrigins("https://app.mycompany.com", "https://admin.mycompany.com").AllowCredentials();
Real-world example A security audit flags a CORS configuration using SetIsOriginAllowed(_ => true) combined with AllowCredentials as a critical vulnerability, since it effectively allows any malicious website to make authenticated API calls using a victim's session cookies.

Common follow-ups: How would an attacker actually exploit an overly permissive CORS + credentials configuration?;What's the safe way to support multiple known frontend origins instead?

Authentication;Global Exception Handling & Middleware

How would you configure CORS to allow any subdomain of a known domain (like *.mycompany.com) using SetIsOriginAllowed with custom matching logic?

Advanced
Since WithOrigins() requires exact origin string matches, allowing a dynamic pattern like any subdomain requires SetIsOriginAllowed(Func<string, bool>) with custom logic checking the origin's host suffix, carefully validated to avoid accidentally matching an attacker-controlled domain that merely contains the expected substring (like evilmycompany.com) rather than genuinely being a subdomain.
policy.SetIsOriginAllowed(origin => {
    var uri = new Uri(origin);
    return uri.Host.Equals("mycompany.com", StringComparison.OrdinalIgnoreCase) ||
           uri.Host.EndsWith(".mycompany.com", StringComparison.OrdinalIgnoreCase);
}).AllowCredentials();
Real-world example A multi-tenant SaaS platform where each customer gets a subdomain (customer1.mycompany.com) uses carefully-validated SetIsOriginAllowed matching logic to permit any valid tenant subdomain without needing to update the CORS policy every time a new customer signs up.

Common follow-ups: What's the risk of a naive string.Contains() check instead of proper suffix matching here?;How would you unit test this custom origin-matching logic thoroughly?

Multiple Inheritance & MRO;Authentication

How do you configure CORS to expose custom response headers (like X-Total-Count for pagination) to client-side JavaScript?

Intermediate
By default, JavaScript can only read a small set of 'safe-listed' response headers from a cross-origin response (Content-Type, Content-Length, etc.) -- any custom headers your API returns are invisible to client JS unless explicitly exposed via WithExposedHeaders() in the CORS policy, which adds them to the Access-Control-Expose-Headers response header.
builder.Services.AddCors(options => {
    options.AddPolicy("MyPolicy", policy => {
        policy.WithOrigins("https://app.mycompany.com")
              .WithExposedHeaders("X-Total-Count", "X-Page-Number");
    });
});

// Now client JS can read: response.headers.get('X-Total-Count')
Real-world example A paginated API returning results with a custom X-Total-Count header initially confused frontend developers who saw the header in browser dev tools' network tab but couldn't read it via fetch() in JavaScript -- resolved by adding it to WithExposedHeaders.

Common follow-ups: Why are only a small set of headers readable by default without this configuration?;How does this differ for same-origin requests, which have no such restriction?

RESTful Web APIs & Controllers;Content Negotiation & Output Formatters

How does CORS interact with an API gateway or reverse proxy sitting in front of multiple backend microservices?

Advanced
When a browser calls through an API gateway acting as a single origin (routing to multiple backend services internally), CORS only needs to be configured once at the gateway layer since that's the origin the browser actually communicates with -- backend microservices behind the gateway typically don't need their own CORS configuration since they're never called directly cross-origin by the browser, simplifying CORS management significantly in a microservices architecture.
// Browser only ever talks to the gateway origin:
// fetch('https://api.mycompany.com/orders')  -- gateway handles CORS
// Gateway internally routes to http://orders-service:8080 (no CORS needed here,
// since this is server-to-server, not browser-to-server)
Real-world example A microservices platform configures CORS exactly once at their API gateway, eliminating the need to duplicate and maintain consistent CORS policies across dozens of individual backend services that the browser never contacts directly.

Common follow-ups: What CORS considerations remain if a gateway does server-side aggregation calling multiple backends?;How does this simplification break down if some services are exposed directly, bypassing the gateway?

Hosting Models: Kestrel IIS & Reverse Proxies;Endpoint Metadata Route Constraints & Templates

What is the difference between CORS and CSRF protection, and why doesn't a strict CORS policy alone fully protect a cookie-authenticated endpoint?

Intermediate
CSRF exploits the browser's automatic inclusion of credentials (cookies) on requests to any site, regardless of CORS -- a properly restrictive CORS policy prevents a malicious site's JavaScript from reading the response of a cross-origin request, but a simple form-based CSRF attack (not requiring the attacker to read the response, just trigger a state-changing action) can still succeed even with strict CORS, meaning dedicated anti-forgery tokens remain necessary for full protection of cookie-authenticated endpoints.
<!-- A malicious site can still trigger this request (CSRF), even with strict CORS,
     because a simple form POST doesn't require reading the response -->
<form action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="amount" value="10000">
</form>
<!-- CORS prevents the attacker's JS from READING the response, but the transfer may still happen -->
Real-world example A security review clarifies to the team that their strict CORS policy protects their API from cross-origin JavaScript reading sensitive responses, but doesn't fully protect a cookie-authenticated form-based endpoint from CSRF, which still requires anti-forgery tokens.

Common follow-ups: How do anti-forgery tokens provide CSRF protection that CORS can't?;Why is SameSite cookie configuration also relevant here?

Security Headers Antiforgery & CSRF Protection;Authentication

Showing 1–10 of 15