Summary
CorsOptions.AllowedHeaders ships as [ "content-type", "authorization" ], but both React apps
send a tenant header on every request. tenant is not a CORS-safelisted request header, so
with the restricted policy (AllowAll: false) the browser's preflight rejects it and every
cross-origin call from either front-end fails before it reaches the API.
Nobody hits this in a default dev run because appsettings.Development.json overrides
CorsOptions.AllowAll to true (which implies AllowAnyHeader). It surfaces the moment someone
turns the restricted policy on — which is exactly what appsettings.json and
appsettings.Production.json ship with.
Where
src/Host/FSH.Starter.Api/appsettings.json — CorsOptions.AllowedHeaders
src/Host/FSH.Starter.Api/appsettings.Production.json — same key
src/BuildingBlocks/Web/Cors/Extensions.cs — the else branch calls
WithHeaders(settings.AllowedHeaders), so the list is the whole allow-list
- The header itself: Finbuckle's
HeaderStrategy, and both clients set it (clients/admin,
clients/dashboard)
Reproduce
- Run the API with
CorsOptions:AllowAll=false and CorsOptions:AllowedOrigins:0=http://localhost:5174
- Run
clients/dashboard on 5174 and sign in
- Every API call fails at the preflight:
Request header field tenant is not allowed by Access-Control-Allow-Headers in preflight response
Suggested fix
Add "tenant" to AllowedHeaders in both shipped appsettings. Since the header is mandatory for
this stack rather than optional, the alternative is for AddHeroCors to always append the tenant
header name to the configured list — the same shape as the ETag exposure added in the PR below,
where the framework knows the header is part of its own contract.
A Framework.Tests policy-level assertion (WithHeaders includes tenant when the restricted
branch is built) would keep it from regressing; today nothing in the suite exercises the restricted
CORS branch.
Context
Found while adding ETag/If-Match support for #1359 — same class of bug in the mirror direction
(that one was a missing WithExposedHeaders). Filing separately because it is pre-existing and
independent of that change.
Summary
CorsOptions.AllowedHeadersships as[ "content-type", "authorization" ], but both React appssend a
tenantheader on every request.tenantis not a CORS-safelisted request header, sowith the restricted policy (
AllowAll: false) the browser's preflight rejects it and everycross-origin call from either front-end fails before it reaches the API.
Nobody hits this in a default dev run because
appsettings.Development.jsonoverridesCorsOptions.AllowAlltotrue(which impliesAllowAnyHeader). It surfaces the moment someoneturns the restricted policy on — which is exactly what
appsettings.jsonandappsettings.Production.jsonship with.Where
src/Host/FSH.Starter.Api/appsettings.json—CorsOptions.AllowedHeaderssrc/Host/FSH.Starter.Api/appsettings.Production.json— same keysrc/BuildingBlocks/Web/Cors/Extensions.cs— theelsebranch callsWithHeaders(settings.AllowedHeaders), so the list is the whole allow-listHeaderStrategy, and both clients set it (clients/admin,clients/dashboard)Reproduce
CorsOptions:AllowAll=falseandCorsOptions:AllowedOrigins:0=http://localhost:5174clients/dashboardon 5174 and sign inRequest header field tenant is not allowed by Access-Control-Allow-Headers in preflight responseSuggested fix
Add
"tenant"toAllowedHeadersin both shippedappsettings. Since the header is mandatory forthis stack rather than optional, the alternative is for
AddHeroCorsto always append the tenantheader name to the configured list — the same shape as the
ETagexposure added in the PR below,where the framework knows the header is part of its own contract.
A
Framework.Testspolicy-level assertion (WithHeadersincludestenantwhen the restrictedbranch is built) would keep it from regressing; today nothing in the suite exercises the restricted
CORS branch.
Context
Found while adding
ETag/If-Matchsupport for #1359 — same class of bug in the mirror direction(that one was a missing
WithExposedHeaders). Filing separately because it is pre-existing andindependent of that change.