443 <--> 80 - Seamlessly moving requests in and out of SSL
Sometimes you feel secure, sometimes you don’t. Better put, sometimes a page needs to be secured and sometimes it does not.
One of the things I wanted to do on a recent project was avoid unnecessary page encryption when the content did not require it to be. This may sound like a silly problem but when you consider that in the logical click stream of a user they may go from a page with sensitive data to a non-sensitive page and then back a forth between pages that contain secret information, you can see where you are wasting cycles encrypting pages that don’t need it.
This seemed to me like a common problem and I expected that the IIS would have an easy way to deal with this problem and while IIS does allow you to require SSL for a specific file it does not fail with elegance. By that I mean that when you visit the page which requires SSL using a normal HTTP session, you get a server error (Http status codes 403.4, 403.5 I think) that tells you this page must be viewed securely. While for some users this is not a big deal, just make the change to the URL – most people get really confused at this point; and heck if the darn thing knew it needed to be secure then why not just become secure. Furthermore, when considering this challenge outside my personal scope I knew that going the IIS route for this solution did not seem the best path because in lots of cases developers don’t have access to make IIS changes. So as I venture to find a way to make my application do this I am quite sure that ASP.NET has some great, built-in functionality which will do for me what I am attempting; after much searching I came to the conclusion that Request.IsSecureConnection is as good as it gets in the framework.
Other people have proposed solutions in the past, today I even ran across one which prompted me to write this; Matt Sollars has an excellent two part article on Code Project which details his solution to this problem involving httpModules and extending the configuration of asp.net.
I actually rolled a solution similar to Matt’s but was unhappy with the general complexity of it; I wanted something simple and the problem scope seemed so limited that there had to be some way to achieve this in a relatively performant manner without having to write a lot of code.
OK, that is a lot of build up, now to the point…
I found a way by extending the Page class that you can automatically move people in and out of secure pages with as little as one line of code per page! Here is how you do it.
First thing you will need to do is add some code to your base Page class; almost every single ASP.NET tips/tricks/good practices/yada/yada/yada article tells you that you should extend System.Web.UI.Page with common functionality; if you are not doing this already, shame on you.
To the base page class add a private boolean field to store the data indicating whether a page is secure
private bool _RequireSSL;
Also add a property which wraps this field
[Browsable(true)]
[Description("Indicates whether or not this page should be forced into or out of SSL")]
public virtual bool RequireSSL
{
get
{
return _RequireSSL;
}
set
{
_RequireSSL = value;
}
}
Note: You will notice that the property is decorated with a couple of attributes, the first, “Browsable” tells VS.NET to show this property in the design time property window allowing you to indicate in that window what the value of the property would be, doing this can make things a bit easier and save you even needing to write the single line of code per page needed to implement the functionality; setting the property effectively writes the code for you. The “Description” attribute tells VS.NET what text should show at the bottom of the Properties window when this property is selected.
Next, we are going to add the actual method to our code which will do the magic. You will notice that this method has two other attributes, the first will tell VS.Net when debugging to skip over this part, no need to see it; it works. The second attribute indicates that we only want to run this code when we have compiled with a SECURE compilation constant defined, this saves us having to deal with SSL certs and such on development machines as we can define that constant only in build configurations that will be deployed to an environment with the certificate such as staging or production.
[System.Diagnostics.DebuggerStepThrough()]
[System.Diagnostics.Conditional("SECURE")]
private void PushSSL()
{
const string SECURE = "https://";
const string UNSECURE = "http://";
//Force required into secure channel
if(RequireSSL && Request.IsSecureConnection==false)
Response.Redirect(Request.Url.ToString().Replace( UNSECURE , SECURE ));
//Force non-required out of secure channel
if(!RequireSSL && Request.IsSecureConnection==true)
Response.Redirect(Request.Url.ToString().Replace( SECURE , UNSECURE ));
}
The logic here is quite simple, if the RequireSSL property is set to TRUE and the Request is not a secure connection then we need to perform a redirect. That redirect will take the Request.Url which is the full URL of the request, convert it to a string and then replace http:// with https:// and send the user on to the https version of the page. The second conditional statement does the same thing only in reverse, taking a user out of SSL if the page is not required to be secure. You could toy with the actual string replacement if you wish, for example you might want to only analyze the first few characters of a string in the case the some form of "http" is embedded later in your URL (maybe you have other URLs; in your URL) that is up to you – for the sake of making it as easy to understand as possible I chose the simplest route.
Now we have our field, our property and our method, the only thing left is the implementation. To make this work for our pages we need to override OnInit in our page class…
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
PushSSL();
}
As you can see from the code above we are really only adding to OnInit, not changing the behavior any as the first thing we do is call the base member. Our second line of code calls our method which will run the process of checking our page is moving it in and out of the secure channel.
Now we have all of the pieces in place, to implement this on an actual page there is really only one line of code which you can write of let VS.NET write for you; an un-initialized boolean is by default false so unless you are trying to make a page secure there is really no reason you will need to do this. In the case you do need to make a page secure you should set the RequireSSL property equal to True on the page; this should be done in the InitializeComponent method…
private void InitializeComponent()
{
this.RequireSSL = true;
//Other initialization code would be here also
}
The setting of this property can also be achieved by pulling up the design time properties of your page, navigating to the Page member in the property drop down list and setting the property manually. This will write the line of code for you.
Normally this is where professional writers recap and wrap up but I have pretty much said all there is to say, it works…it is not perfect but it does the job, if you are into this kind of thing I would also suggest looking at Matt’s article and deciding what solution is best for you.
related stuff to check out:
MSDN on Conditional Attributes
MSDN on the Browsable Attribute
MSDN on the Description Attribute
The Debugger Step Through Attribute
Matt Sollars solution @ Code Project
HTTP Status Codes
Extending System.Web.UI.Page