Well I didn't have much experience with load balanced environments, so this was a great lesson and learning experience. Turns out it wasn't too bad...in the end. Definitely frustrating, but it now all makes sense.
This post won't cover how to setup the Elastic Load Balancer (ELB) in Amazon's cloud, but it will cover on how I handled redirecting http to https behind the ELB.
Configuration of ELB
- Port 80 forwards to port 80 on the IIS Web Server
- Port 443 forwards to port 80 on the IIS Web Server
- SSL termination is handled on the ELB (ELB has the SSL certificate installed on it)
Configuration of IIS
- Multiple websites listening on port 80, but using host headers to differentiate
- NO SSL certificates installed and nothing listening on port 443
Based off the configuration above, here is the solution I came up with so I can switch how SSL is handled from environment to environment (dev, stage, production) since they are not all load balanced.
First off I added an appSetting in the web.config file of the application called HostingEnvironment
<add key="HostingEnvironment" value="LOCALHOST"/>
Second, I created a method to handle redirecting based off what environment I was in. For example my valid values for right now testing this out are
- LOCALHOST
- DEV
- EC2LB
- NOSSL
Third, I created a method in a Util class that handled the redirect. Here is the entire code of the method. Put this in whatever class you want.
public static void IsSecurePage(bool isSecure)
{
string HostingEnvironment = System.Configuration.ConfigurationManager.AppSettings["HostingEnvironment"].ToString();
switch (HostingEnvironment.ToLower())
{
case "ec2lb":
if (isSecure)
{
if (HttpContext.Current.Request.Headers["X-FORWARDED-PROTO"].ToString().ToLower() != "https")
{
HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Url.AbsoluteUri.Replace("http://", "https://"));
}
}
else
{
if (HttpContext.Current.Request.Headers["X-FORWARDED-PROTO"].ToString().ToLower() == "https")
{
HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Url.AbsoluteUri.Replace("https://", "http://"));
}
}
break;
case "dev":
if (isSecure)
{
if (!HttpContext.Current.Request.IsSecureConnection)
{
HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Url.AbsoluteUri.Replace("http://", "https://"));
}
}
else
{
if (HttpContext.Current.Request.IsSecureConnection)
{
HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Url.AbsoluteUri.Replace("https://", "http://"));
}
}
break;
case "localhost":
if (isSecure)
{
if (!HttpContext.Current.Request.IsSecureConnection)
{
HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Url.AbsoluteUri.Replace("http://", "https://"));
}
}
else
{
if (HttpContext.Current.Request.IsSecureConnection)
{
HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Url.AbsoluteUri.Replace("https://", "http://"));
}
}
break;
case "nossl":
//TODO: do nothing because we don't care about ssl for this environment
break;
default:
if (isSecure)
{
if (!HttpContext.Current.Request.IsSecureConnection)
{
HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Url.AbsoluteUri.Replace("http://", "https://"));
}
}
else
{
if (HttpContext.Current.Request.IsSecureConnection)
{
HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Url.AbsoluteUri.Replace("https://", "http://"));
}
}
break;
}
}
Notice the difference for the EC2LB environment. Instead of looking for the IsSecureConnection property, you need to look for the X-FORWARDED-PROTO header. Otherwise, you would just get stuck in a loop because to the IIS web server there is not a secure connection between the ELB and the Web Server. To IIS eveything is coming in on port 80.
To use the method above in your code you can do something like this (assuming you have a class called Util that you put the method above in)
Util.IsSecurePage(true)
You most likely will put this line of code in the Pre_Render event on the page you want to be secure or not secure. In my overall solution it is a bit more than that, but I'm not covering that as I wanted to
make sure you grasp that when behind the ELB you need to look for the header X-FORWARDED-PROTO to determine if you want to redirect or not.
From environment to environment just switch the value of the appSetting key HostingEnvironment to whatever you came up with. This way you don't need to change your code.
Hope this helps out and good luck with the load balancing.