From 5f422e04cf02d6459aefa361a521c8bf2b3425e8 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Fri, 15 May 2026 00:08:13 -0700 Subject: [PATCH 1/2] feat: redirect www.essentialcsharp.com to apex domain Adds a 301 redirect in the production middleware pipeline that strips the 'www.' prefix and redirects to the apex domain. Runs after UseForwardedHeaders so the real Host header is available behind the Azure Container Apps ingress proxy. --- EssentialCSharp.Web/Program.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/EssentialCSharp.Web/Program.cs b/EssentialCSharp.Web/Program.cs index 48094a92..52c6e3aa 100644 --- a/EssentialCSharp.Web/Program.cs +++ b/EssentialCSharp.Web/Program.cs @@ -493,6 +493,20 @@ await McpJsonRpcResponseWriter.WriteErrorAsync( app.UseSecurityHeadersMiddleware(new SecurityHeadersBuilder() .AddDefaultSecurePolicy() .AddContentSecurityPolicy(csp)); + + // Redirect www.essentialcsharp.com → essentialcsharp.com (permanent 301) + // Must be after UseForwardedHeaders so the Host header reflects the real hostname. + app.Use(async (context, next) => + { + if (context.Request.Host.Host.StartsWith("www.", StringComparison.OrdinalIgnoreCase)) + { + string apexHost = context.Request.Host.Host[4..]; + string redirectUrl = $"{context.Request.Scheme}://{apexHost}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}"; + context.Response.Redirect(redirectUrl, permanent: true); + return; + } + await next(context); + }); } else { From d053d3e312a69c0af8f069743b88e169257b4f8e Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Fri, 15 May 2026 19:38:26 -0700 Subject: [PATCH 2/2] fix: harden www redirect against open redirect --- EssentialCSharp.Web/Program.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/EssentialCSharp.Web/Program.cs b/EssentialCSharp.Web/Program.cs index 52c6e3aa..5d96e99f 100644 --- a/EssentialCSharp.Web/Program.cs +++ b/EssentialCSharp.Web/Program.cs @@ -430,6 +430,17 @@ await context.HttpContext.Response.WriteAsync( // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { + SiteSettings siteSettings = app.Services.GetRequiredService>().Value; + if (!Uri.TryCreate(siteSettings.BaseUrl, UriKind.Absolute, out Uri? configuredBaseUri)) + { + throw new InvalidOperationException($"Invalid {SiteSettings.SectionName}:{nameof(SiteSettings.BaseUrl)} value: '{siteSettings.BaseUrl}'."); + } + string apexHost = configuredBaseUri.Host.StartsWith("www.", StringComparison.OrdinalIgnoreCase) + ? configuredBaseUri.Host[4..] + : configuredBaseUri.Host; + string wwwHost = $"www.{apexHost}"; + string redirectAuthority = new UriBuilder(configuredBaseUri) { Host = apexHost }.Uri.GetLeftPart(UriPartial.Authority); + app.UseExceptionHandler(exceptionApp => { exceptionApp.Run(async context => @@ -494,14 +505,13 @@ await McpJsonRpcResponseWriter.WriteErrorAsync( .AddDefaultSecurePolicy() .AddContentSecurityPolicy(csp)); - // Redirect www.essentialcsharp.com → essentialcsharp.com (permanent 301) + // Redirect configured www host to configured apex host (permanent 301). // Must be after UseForwardedHeaders so the Host header reflects the real hostname. app.Use(async (context, next) => { - if (context.Request.Host.Host.StartsWith("www.", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(context.Request.Host.Host, wwwHost, StringComparison.OrdinalIgnoreCase)) { - string apexHost = context.Request.Host.Host[4..]; - string redirectUrl = $"{context.Request.Scheme}://{apexHost}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}"; + string redirectUrl = $"{redirectAuthority}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}"; context.Response.Redirect(redirectUrl, permanent: true); return; }