2

I'm working with a ASP.NET WebForms application running on .NET Framework 4.7.2 which is sitting behind an Azure Application Gateway. The gateway performs SSL hand-off so is adding a X-Forwarded-Proto="https" header.

I also have a .NET Core API in which I can configure

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseForwardedHeaders();
    app.UseHsts();
    app. //...
}

This is documented here, but is specific to .NET Core.

I've been unable to find an equivalent of UseForwardedHeaders for .NET Framework - is there an equivalent?

Also, the WebForms application is running on OWIN, so I can do something like this, but I'm not sure how to complete it.

public void Configuration(IAppBuilder app)
{
    app.Use(async (context, next) =>
            {
                var forwardedProto = context.Request.Headers["X-Forwarded-Proto"];

                if (forwardedProto.ToLower() == "https")
                {
                    // what now?
                }

                await next();
            });
}

Is there an out-of-the-box solution for .NET Framework, or advice on how best to handle this?

awj
  • 6,866
  • 9
  • 61
  • 102
  • That only depends on what you want to do later with this. Take a look [here](https://stackoverflow.com/questions/30196569/request-url-scheme-gives-http-instead-of-https-on-load-balanced-site) for example ideas. In our systems, we do not rely on the header (as it can be forged) but rather on an additional bool setting that overrides the scheme (so that whenever http comes the app knows that it's https). – Wiktor Zychla Feb 26 '21 at 09:21
  • Thanks, but the Application Gateway is configured to redirect all HTTP requests to HTTPS, so no HTTP requests _from the client_ reach the application behind the Application Gateway. – awj Feb 26 '21 at 09:28
  • I know that, http requests reach your server (as the gateway forwards all incoming https requests as http). Still, the question is, what you expect from the *what now*? .NET Framework doesn't have `Scheme` under `Request` so you can't modify that. Means that you can pretty much leave it as is or rewrite to somewhere else (e.g `context.Items`) so that it's there if someone needs it down the pipeline. – Wiktor Zychla Feb 26 '21 at 11:23

1 Answers1

5

I ended up building my own middleware.

public static class UseForwardedHeadersExtension
{
    private const string ForwardedHeadersAdded = "ForwardedHeadersAdded";

    /// <summary>
    /// Checks for the presence of <c>X-Forwarded-For</c> and <c>X-Forwarded-Proto</c> headers, and if present updates the properties of the request with those headers' details.
    /// </summary>
    /// <remarks>
    /// This extension method is needed for operating our website on an HTTP connection behind a proxy which handles SSL hand-off. Such a proxy adds the <c>X-Forwarded-For</c>
    /// and <c>X-Forwarded-Proto</c> headers to indicate the nature of the client's connection.
    /// </remarks>
    public static IAppBuilder UseForwardedHeaders(this IAppBuilder app)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        // No need to add more than one instance of this middleware to the pipeline.
        if (!app.Properties.ContainsKey(ForwardedHeadersAdded))
        {
            app.Properties[ForwardedHeadersAdded] = true;

            app.Use(async (context, next) =>
                    {
                        var request = context.Request;

                        if (request.Scheme != Uri.UriSchemeHttps && String.Equals(request.Headers["X-Forwarded-Proto"], Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
                        {
                            var httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
                            var serverVars = httpContext.Request.ServerVariables;
                            serverVars["HTTPS"] = "on";
                            serverVars["SERVER_PORT_SECURE"] = "1";
                            serverVars["SERVER_PORT"] = "443";
                            serverVars["HTTP_HOST"] = request.Headers.ContainsKey("X-Forwarded-Host") ? request.Headers["X-Forwarded-Host"] : serverVars["HTTP_HOST"];
                        }

                        await next.Invoke().ConfigureAwait(false);
                    });
        }

        return app;
    }
}
awj
  • 6,866
  • 9
  • 61
  • 102
  • This was a good solution. We also added known proxies and known networks options (much like the official ASP.NET Core version) to this so that we could limit the load balancer client to known address ranges. – Mike Smith Mar 04 '21 at 17:40