1

I have the following attribute to make sure that the remote site page opens in https mode.

public class RemoteRequireHttpsAttribute : RequireHttpsAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentException("Filter Context");
            }

            if (filterContext != null && filterContext.HttpContext != null)
            {
                if (filterContext.HttpContext.Request.IsLocal)
                {
                    return;
                }
                else
                {
                    string val = ConfigurationManager.AppSettings["RequireSSL"].Trim();
                    bool requireSsl = bool.Parse(val);
                    if (!requireSsl)
                    {
                        return;
                    }
                }
            }

            base.OnAuthorization(filterContext);
        }
    }

Local development now work normal since i don't want it to open in https mode.

Dev site opens the page in https mode - no issues here (single node).

Where as the production (load balanced - 2 nodes) site that i am currently setting up is giving me following error. Please note that dev and prod sites have the same setings and web.config

The page isn't redirecting properly

Firefox has detected that the server is redirecting the request for this address in a way that will never complete.

This problem can sometimes be caused by disabling or refusing to accept cookies.

Dev site url is like http://dev.datalab.something.org

Prod site url is like http://datalab.something.org

And here is the call

[RemoteRequireHttps]
public ActionResult Index(string returnUrl, string error)

What am i missing here?

Update 1: My admin has confirmed that the SSL termination has been setup at the lad balancer evel. I have looked at the iis site setup and i don't see https bindings. I only see http binding. Does he need to setup https bindings as well?

Update 2: @AlexeiLevenkov pointed me to the right direction and this post had the code that I utilized and it is working. MOVED the code into a separate answer.

learning...
  • 3,104
  • 10
  • 58
  • 96
  • It doesn't appear you are redirecting the user back to an HTTPS conneciton, you are only denying access? – Erik Philips Nov 10 '14 at 17:50
  • You have some sort of infinite loop where your pages are being redirected back and forth. Use your browser's debugging tools to determine what pages it's jumping between, then inspect the configuration/code for those pages. – mason Nov 10 '14 at 17:51
  • RemoteRequireHttps inherits from RequireHttpsAttribute. This does the redirection. @mason I have the same code on both dev and prod sites. Dev site displays the page in https mode just fine. No redirection issues. Only one attribute is applied to this action. – learning... Nov 10 '14 at 17:53
  • Look at IIS log for incoming requests... I there is a good chance that all of them are HTTP due to [SSL termination](http://en.wikipedia.org/wiki/SSL_termination_proxy) somewhere before your server. – Alexei Levenkov Nov 10 '14 at 17:55
  • What is the problem with the dev machine and https - just configure it correctly and you do not need such a switch? – TGlatzer Nov 10 '14 at 17:55
  • You *are* getting redirection issues, otherwise Firefox would not have given you the error message that it did. – mason Nov 10 '14 at 17:56
  • Yes @mason. But the thing i am not understanding is that why i am getting this issue since both dev and prod has the same code base. The only difference between the sites is that prod is load balanced. – learning... Nov 10 '14 at 17:58
  • @learning... This isn't the point for understanding. This is the time when you should figure out *why* it's happening, then you can understand after you've done the necessary investigation. Start with looking at your browser tools and see which page it's redirecting back and forth between. – mason Nov 10 '14 at 17:59
  • 1
    @learning...most of the answer here - "that prod is load balanced." The rest will be clear when you check if load balancer configured to perform SSL termination. Read ["x-forwarded-proto"](http://stackoverflow.com/questions/13111080/what-is-a-full-specification-of-x-forwarded-proto-http-header) header instead and your life will be good. – Alexei Levenkov Nov 10 '14 at 17:59
  • I have updated the question with update 1. – learning... Nov 10 '14 at 19:40
  • I have updated the question with update #2. @AlexeiLevenkov please put your comment as an answer and i'll accept it. Thanks all for helping. – learning... Nov 10 '14 at 22:12
  • Converted... You may want to move your Update 2 into separate answer as it generally not recommended to put answers into questions. – Alexei Levenkov Nov 10 '14 at 23:07
  • I have moved Update #2 from the question into a separate answer. – learning... Nov 10 '14 at 23:25

3 Answers3

2

Your site is behind load balancer that does SSL termination - so as result all incoming traffic to your site is HTTP irrespective what user sees. This causes your code to always try to redirect to HTTPS version and hence infinite loop.

Options to fix:

  • Usually load balancer that does SSL termination will forward original IP/protocol via custom headers. x-forwarded-proto and x-forwarded-for are common ones to be used for this purpose. You may need to check with network admins if these headers used or some additional configuration is needed
  • Alternatively you can turn off SSL termination, but it will put additional load on your server.
  • One can also configure load balancer to talk to server with the same protocol as incoming request.

How to investigate such issue:

  • Look at http debugger (like Fiddler) to see if you are getting 30x redirects requests in a loop. If no redirects - likely code is wrong.
  • If you see repeated redirects it likely means site does not see actual request information - could be protocol, path cookies missing.
  • To continue investigation see what devices are between user and server (CDN, proxies, load balancer,...) - each have good chance to loose some date or transform protocols.
Community
  • 1
  • 1
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
1

Not that I am against writing nice custom attributes, would it not make sense to perhaps perform the redirect in the web.config and use transformations available for the web.config to change the enabled value below from false to true for production deploys?

<rewrite>
  <rules>
    <rule name="SSL_ENABLED" enabled="false" stopProcessing="true">
      <match url="(.*)" />
      <conditions>
        <add input="{HTTPS}" pattern="^OFF$" />
      </conditions>
      <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" appendQueryString="true" redirectType="Permanent" />
    </rule>
  </rules>
</rewrite>
Louis Lewis
  • 1,298
  • 10
  • 25
  • OP gets redirection already, it just does not end... So Web.Config change will likely behave exactly the same (also it is somewhat simpler). – Alexei Levenkov Nov 10 '14 at 17:57
  • I use this code in our production app, when we are working locally we simply have the enabled value set to false as in the example above, when we deploy to our production server, we simply change the value to true and then deploy. we have also tested this on IE, Firefox, Chrome and also on the most popular mobile browsers and it works like a charm for us. – Louis Lewis Nov 10 '14 at 18:00
  • Isn't this going to make all the pages https? – learning... Nov 10 '14 at 18:01
  • It is - but there is no reason to do non https if you have SSL certificates – TGlatzer Nov 10 '14 at 18:03
  • For our requirement that was the case, however you could edit the match url if you want only specific pages secured. I found no harm in securing all pages, customers like seeing the secured icon in browsers :) – Louis Lewis Nov 10 '14 at 18:05
  • I have done this for our intranet site. My requirements are to not make content pages https. Only form pages need to be https. So just just doing as being told. – learning... Nov 10 '14 at 18:20
  • This won't work with SSL Termination beyond the web server, which is what the questionee has. All traffic to IIS will be HTTP..... – James Joyce Jun 01 '17 at 08:52
1

Moving the fix into a separate answer as noted by @AlexeiLevenkov.

public class RemoteRequireHttpsAttribute : RequireHttpsAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentException("Filter Context");
            }

            if(filterContext.HttpContext != null)
            {
                if (filterContext.HttpContext.Request.IsSecureConnection)
                {
                    return;
                }

                var currentUrl = filterContext.HttpContext.Request.Url;
                if (currentUrl.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.CurrentCultureIgnoreCase))
                {
                    return;
                }

                if (string.Equals(filterContext.HttpContext.Request.Headers["X-Forwarded-Proto"], "https", StringComparison.InvariantCultureIgnoreCase))
                {
                    return;
                }

                if (filterContext.HttpContext.Request.IsLocal)
                {
                    return;
                }

                var val = ConfigurationManager.AppSettings["RequireSSL"].Trim();
                var requireSsl = bool.Parse(val);
                if (!requireSsl)
                {
                    return;
                }
            }

            base.OnAuthorization(filterContext);
        }
    }

and i have updated the ExitHttps attribute as well. This was having the similar issues...

public class ExitHttpsAttribute : FilterAttribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentException("Filter Context");
            }

            if (filterContext.HttpContext == null)
            {
                return;
            }

            var isSecure = filterContext.HttpContext.Request.IsSecureConnection;

            var currentUrl = filterContext.HttpContext.Request.Url;
            if (!isSecure && currentUrl.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.CurrentCultureIgnoreCase))
            {
                isSecure = true;
            }

            if (!isSecure && string.Equals(filterContext.HttpContext.Request.Headers["X-Forwarded-Proto"], "https", StringComparison.InvariantCultureIgnoreCase))
            {
                isSecure = true;
            }

            if (isSecure)
            {
                //in these cases keep https
                // abort if a [RequireHttps] attribute is applied to controller or action
                if (filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof (RequireHttpsAttribute), true).Length > 0)
                {
                    isSecure = false;
                }

                if (isSecure && filterContext.ActionDescriptor.GetCustomAttributes(typeof (RequireHttpsAttribute), true).Length > 0)
                {
                    isSecure = false;
                }

                // abort if a [RetainHttps] attribute is applied to controller or action
                if (isSecure && filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof (RetainHttpsAttribute), true).Length > 0)
                {
                    isSecure = false;
                }

                if (isSecure && filterContext.ActionDescriptor.GetCustomAttributes(typeof (RetainHttpsAttribute), true).Length > 0)
                {
                    isSecure = false;
                }

                // abort if it's not a GET request - we don't want to be redirecting on a form post
                if (isSecure && !String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
                {
                    isSecure = false;
                }
            }

            if (!isSecure)
            {
                return;
            }

            // redirect to HTTP
            var url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
            filterContext.Result = new RedirectResult(url);
        }
    }
learning...
  • 3,104
  • 10
  • 58
  • 96