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