37

I am testing a new load balanced staging site and the https is set up at the load balancer level, not at the site level. Also, this site will be always https so i don't need remote require https attributes etc. The url displays https but it is not available in my code. I have a few issues due to this reason

Request.Url.Scheme is always http:

public static string GetProtocol()
        {
            var protocol = "http";
            if (HttpContext.Current != null && HttpContext.Current.Request != null)
            {
                protocol = HttpContext.Current.Request.Url.Scheme;
            }
            return protocol;
        }

Same thing with this base url, protocol is http

public static string GetBaseUrl()
        {
            var baseUrl = String.Empty;

            if (HttpContext.Current == null || HttpContext.Current.Request == null || String.IsNullOrWhiteSpace(HttpRuntime.AppDomainAppPath)) return baseUrl;

            var request = HttpContext.Current.Request;
            var appUrl = HttpRuntime.AppDomainAppVirtualPath;

            baseUrl = string.Format("{0}://{1}{2}", request.Url.Scheme, request.Url.Authority, appUrl);

            if (!string.IsNullOrWhiteSpace(baseUrl) && !baseUrl.EndsWith("/"))
                baseUrl = String.Format("{0}/", baseUrl);

            return baseUrl;
        }

Now the biggest issue is referencing js files and google fonts referenced in the style sheets. I am using // here without http or https but these are treated as http and i see mixed content blocked message in FireBug.

How can i overcome this issue?

learning...
  • 3,104
  • 10
  • 58
  • 96

4 Answers4

24

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.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • X-FORWARDED-FOR should hold the original client IP(s) if the load balancer is correctly configured. – Eric J. May 12 '15 at 16:49
  • 1
    `Absolute Uri` gives me the url withut https `HttpContext.Request.Headers["X-Forwarded-Proto"]` is empty `HttpContext.Request.Headers["X-Forwarded-For"]` gives me an ip only, without https – learning... May 12 '15 at 17:01
  • 1
    @learning... If you want folks from SO to talk to your networking/operations team and find how load balancers are configured and what (if any) headers sent to your site please make sure to provide contact information in your post (also it probably would be not useful for most visitors). Alternatively you can talk to them directly. – Alexei Levenkov May 12 '15 at 17:16
  • and thats exactally what i have been doing. I don't know if my additional info (comment) will help some one or not, your answer did help me point in the right direction. Some one may get into the same issue and they may see the same results as me, when load balancer is configured in a certain way... Thanks a lot and i'll be marking your answer as helpful shortly. – learning... May 12 '15 at 17:48
  • I have the same problem. In my case it is a proxy server that converts http to https but request.Url.Scheme always gets http. so I guess request.Url is useless in this case and I ended up making a fixed string "https" for the Url scheme. – L.L. Jul 11 '16 at 13:52
13

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>
Bouke
  • 11,768
  • 7
  • 68
  • 102
  • Even though i am not doing MVC any more, i really like what you have done. Thanks for the update! – learning... Jan 14 '19 at 01:19
  • This is very appealing, but can you explain the "...really sure these headers are under your control..." part a bit more? I'm pretty new to C# MVC and want something that will be reliable on AWS with & without a load balancer, as well as on localhost. Current codebase is just storing URLs in web.config. – Eric Hirst Apr 16 '19 at 22:32
  • 1
    Headers can be set by the client, including those starting with `X-*`. So if there’s no load balancer, the client can control those. Thus, then you cannot rely on those headers for security concerns. – Bouke Apr 17 '19 at 05:41
  • Thanks for sharing this. For anyone wondering, Azure Application Gateway automatically provides these headers: https://learn.microsoft.com/en-us/azure/application-gateway/how-application-gateway-works#modifications-to-the-request – JohnLBevan Aug 20 '21 at 14:36
3

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
Alejo
  • 1,913
  • 3
  • 26
  • 44
  • 2
    Bear in mind URL referrer isn't always reliable / can't always be trusted: https://stackoverflow.com/questions/6023941/how-reliable-is-http-referer – keithl8041 Feb 15 '18 at 20:30
1

According to https://learn.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;
    }
Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170