Skip to content

// Security · 07

CORS

Other site's scriptleads toBrowser checksleads toResponse hidden

Tells the browser which other websites may read your API responses.

How it fits together

CORS structure«client»Shop pagehttps://shop.example.comOrders APIallows shop origin onlyOther siteorigin evil.example1 preflight2 allow origin3 real request4 read blocked
uses / callspublishes / notifiesfails / error path· numbered steps replay in order
  • Shop page uses / calls Orders API (1 preflight)
  • Orders API publishes / notifies Shop page (2 allow origin)
  • Shop page uses / calls Orders API (3 real request)
  • Other site fails / error path Orders API (4 read blocked)

Spot it

  • Console says "blocked by CORS policy"
  • Works in curl, fails in the browser
  • Preflight OPTIONS returns 401 or 405

It costs

Preflight adds a request, cut by max-age; a list that is too tight breaks real clients.

In code

var builder = WebApplication.CreateBuilder(args);​builder.Services.AddCors(options =>{    options.AddPolicy("storefront", policy =>    {        policy            .WithOrigins("https://shop.example.com")            .WithMethods("GET", "POST", "PUT")            .WithHeaders("Content-Type", "Authorization")            .AllowCredentials()            .SetPreflightMaxAge(TimeSpan.FromMinutes(10));    });});​var app = builder.Build();app.UseCors("storefront");
Go deeperThe full explanation and what to ask for · 1 min readExpand

CORS relaxes the same-origin policy, and the browser enforces it. A page on one origin (scheme, host and port) that fetches from another origin may read the response only if that server says so with Access-Control-Allow-Origin. The server still runs the request; the browser just hides the result from the page. That is why curl ignores CORS entirely.

Non-simple requests (JSON bodies, custom headers, PUT or DELETE) first send a preflight OPTIONS asking permission. Answer it with the exact allowed origin, methods and headers, plus a max-age so browsers cache the answer. Credentialed requests need Access-Control-Allow-Credentials: true, and then the origin must be one exact value; browsers refuse a wildcard in that case.

CORS is not a security control for your API. Authentication and authorization must still be enforced on every endpoint, because non-browser clients ignore CORS, and simple cross-site requests are sent anyway. Keep the origin list short and explicit, and never echo the request Origin header back without checking it against that list. When NOT to bother: a public API with no cookies and no browser callers needs no CORS policy at all.

Ask for it

  • same-origin policy
  • origin (scheme, host, port)
  • preflight (OPTIONS)
  • simple vs non-simple request
  • Access-Control-Allow-Origin
  • credentialed request

Let only https://shop.example.com call /api/* from the browser. Answer preflight OPTIONS with that exact origin, methods GET, POST, PUT, headers Content-Type and Authorization, max-age 600s. Allow credentials for that origin only, never a wildcard. Keep server-side authorization unchanged.