Request.Url.Scheme gives http instead of https on load balanced site

I override the ServerVariables to convince MVC it really is communicating through HTTPS and also expose the user's IP address. This is using the X-Forwarded-For and X-Forwarded-Proto HTTP headers being set by your load balancer.

Note that you should only use this if you're really sure these headers are under your control, otherwise clients might inject values of their liking.

public sealed class HttpOverrides : IHttpModule
{
    void IHttpModule.Init(HttpApplication app)
    {
        app.BeginRequest += OnBeginRequest;
    }

    private void OnBeginRequest(object sender, EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;

        string forwardedFor = app.Context.Request.Headers["X-Forwarded-For"]?.Split(new char[] { ',' }).FirstOrDefault();
        if (forwardedFor != null)
        {
            app.Context.Request.ServerVariables["REMOTE_ADDR"] = forwardedFor;
            app.Context.Request.ServerVariables["REMOTE_HOST"] = forwardedFor;
        }

        string forwardedProto = app.Context.Request.Headers["X-Forwarded-Proto"];
        if (forwardedProto == "https")
        {
            app.Context.Request.ServerVariables["HTTPS"] = "on";
            app.Context.Request.ServerVariables["SERVER_PORT"] = "443";
            app.Context.Request.ServerVariables["SERVER_PORT_SECURE"] = "1";
        }
    }

    void IHttpModule.Dispose()
    {
    }
}

And in Web.config:

<system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
        <add name="HttpOverrides" type="Namespace.HttpOverrides" preCondition="integratedMode" />
    </modules>
</system.webServer>

As you've said HTTPS termination is done at load balancer level ("https is set up at the load balancer level") which means original scheme may not come to the site depending on loadbalancer configuration.

It looks like in your case LB is configured to talk to site over HTTP all the time. So your site will never see original scheme on HttpContext.Request.RawUrl (or similar properties).

Fix: usually when LB, proxy or CDN configured such way there are additional headers that specify original scheme and likely other incoming request parameters like full url, client's IP which will be not directly visible to the site behind such proxying device.


I know this is an old question, but after encountering the same problem, I did discover that if I look into the UrlReferrer property of the HttpRequest object, the values will reflect what was actually in the client browser's address bar.

So for example, with UrlReferrer I got:

Request.UrlReferrer.Scheme == "https"
Request.UrlReferrer.Port == 443

But for the same request, with the Url property I got the following:

Request.Url.Scheme == "http"
Request.Url.Port == 80

According to https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer

When HTTPS requests are proxied over HTTP, the original scheme (HTTPS) may be lost and must be forwarded in a header.

In Asp.Net Core I found ( not sure if it works for all scenarios) that even if request.Scheme is misleadingly shows “http” for original “https”, request.IsHttps property is more reliable. I am using the following code

      //Scheme may return http for https
       var scheme = request.Scheme;
       if(request.IsHttps) scheme= scheme.EnsureEndsWith("S");

     //General string extension   
     public static string EnsureEndsWith(this string str, string sEndValue, bool ignoreCase = true)
    {
        if (!str.EndsWith(sEndValue, CurrentCultureComparison(ignoreCase)))
        {
            str = str + sEndValue;
        }
        return str;
    }

Tags:

C#

Asp.Net Mvc