Skip to content

HTTP Hosting — CORS & Response Compression

Granit.Http.Hosting groups the HTTP host concerns that every web host needs and no other host does: a standardized CORS policy driven by appsettings.json and Brotli/gzip response compression with safe defaults. It absorbs the former Granit.Http.Cors and Granit.Http.ResponseCompression packages — two single-feature packages that were always referenced together (both ship in Granit.Bundle.Essentials).

[DependsOn(typeof(GranitHttpHostingModule))]
public class AppModule : GranitModule { }
{
"Http:Cors": {
"AllowedOrigins": ["https://app.example.com", "https://admin.example.com"],
"AllowCredentials": false
},
"Http:ResponseCompression": {
"EnableForHttps": true
}
}

That is the whole wiring for CORS — no app.UseCors() call. Compression still needs its middleware placed explicitly (ordering matters, see below).

Wildcard origins (*) are rejected at startup in non-development environments to enforce ISO 27001 network access controls.

The module registers an IStartupFilter that prepends UseCors() to the pipeline, so a host that forgets the middleware call no longer ships a CORS policy that is configured but never enforced. When you need to control the middleware position yourself (rare — e.g. CORS after a custom rewriter), opt out and place it manually:

{ "Http:Cors": { "AutoRegisterMiddleware": false } }
app.UseCors(); // manual placement — required once auto-registration is off

With auto-registration disabled and no manual call, a warning is logged at startup: no CORS headers will be emitted.

RuleEnforced atEnvironment
At least one origin requiredStartupAll
Wildcard * forbiddenStartupNon-development
AllowCredentials + wildcard rejectedStartupAll (CORS specification)
PropertyDefaultDescription
AllowedOrigins[]Allowed CORS origins (required, minimum 1)
AllowCredentialsfalseInclude Access-Control-Allow-Credentials: true
AutoRegisterMiddlewaretruePrepend UseCors() via IStartupFilter

The default policy applies AllowAnyHeader() and AllowAnyMethod(), which is standard for REST APIs. Origins are restricted to the configured list.

A typical JSON API response of 50 KB becomes 5–8 KB after Brotli compression. ASP.NET Core ships a response compression middleware, but enabling it safely requires several decisions: which providers, which MIME types, what about HTTPS and the BREACH attack, should SSE streams be compressed? Granit.Http.Hosting makes those decisions for you:

  • Brotli first, gzip fallback — modern clients get the best ratio (15–25 % smaller than gzip alone), older clients still get compression
  • HTTPS compression enabled safely — BREACH is mitigated by Granit’s antiforgery tokens, CORS enforcement, and SameSite cookies. For Bearer-token APIs the attack is not even applicable (no secret in the response body)
  • SSE never compressedtext/event-stream is hardcoded as excluded so Server-Sent Events stream in real time without buffering surprises
  • Sensible MIME list — JSON, HTML, CSS, JS, XML, WASM, and SVG out of the box
  • Fast by defaultCompressionLevel.Fastest balances CPU cost and ratio for API workloads; override per provider if you need maximum compression

Add the middleware before anything that produces a response body — otherwise those responses won’t be compressed:

app.UseGranitResponseCompression(); // first
app.UseOutputCache();
app.UseAuthorization();
app.MapControllers();

Configuration reference — Http:ResponseCompression

Section titled “Configuration reference — Http:ResponseCompression”

Override in appsettings.json only when you have a specific reason:

PropertyDefaultWhen to change
EnableForHttpstrueSet to false only if you sit behind a reverse proxy that already compresses (Cloudflare, Nginx) and want to avoid double-compression
EnableBrotlitrueDisable if all clients are internal tools that don’t send Accept-Encoding: br
EnableGziptrueRarely — gzip is the universal fallback
BrotliLevelFastestOptimal or SmallestSize for batch endpoints where latency matters less than bandwidth
GzipLevelFastestSame reasoning as Brotli

Compressing responses over HTTPS can theoretically enable the BREACH attack — an attacker who can inject content into the same response as a secret (like a CSRF token) could deduce the secret by observing compressed sizes.

In practice, Granit APIs are protected by multiple layers:

  • Antiforgery tokens are randomized per request — the compression oracle cannot converge on a stable value
  • CORS blocks cross-origin requests by default
  • SameSite cookies prevent cross-site request forgery

For REST or GraphQL APIs using Bearer tokens (JWT), BREACH does not apply at all — the token travels in headers, never in the response body.

Server-Sent Events (text/event-stream) are always excluded from compression. This is not configurable — it’s a safety invariant. Compressing an SSE stream would buffer events and break the real-time contract with clients.

WebSocket frames bypass HTTP response compression entirely (the protocol switches after the initial handshake), so no special handling is needed.

CategoryKey typesPackage
ModuleGranitHttpHostingModule
CORS optionsGranitCorsOptions (Http:Cors)Granit.Http.Hosting
CORS extensionsAddGranitCors() (IHostApplicationBuilder)Granit.Http.Hosting
Compression optionsGranitResponseCompressionOptions (Http:ResponseCompression)Granit.Http.Hosting
Compression extensionsAddGranitResponseCompression(), UseGranitResponseCompression()Granit.Http.Hosting