Important: New Version
Please refer to the new version of this module and article here: Switching Between HTTP and HTTPS Automatically: Version 2.
Introduction
Let's face it. Providing sensitive information on a web site is a risk. Many visitors will not give out that kind of data even if they see that the web site claims security. Many more will certainly not reveal their personal details if the warm-and-fuzzy closed padlock isn't visible in their browser window.
Background
Enter Secure Sockets Layer. SSL is a developer's tool for securing the transmission of data. Whether you are encrypting pages for the checkout area of an e-commerce site or you are protecting the personal statistics that your users supply you for marketing; SSL is ideal. A trusted certificate installed on the web server offers visitors, that good feeling of a secure environment.
There are caveats when implementing a web site that makes use of the HTTPS protocol. I'm not referring to the technical nuances you, or a system administrator, must face when installing a certificate on the server. What about simply adding a link from one page to another page that should be secured?
Those of you who have experience with writing web pages that use SSL probably know where I'm going with this. You cannot switch protocols unless you provide an absolute URL. Therefore, in order to allow a visitor to click on a link that should take them to a secure web page, the reference must be absolute.
https://www.codeproject.com/secure/getsensitiveinfo.asp
To make things worse, many browsers download pages referenced by a relative URL with the same protocol as the last request. So, if you had a link in the above file to another page in the root directory, that you wanted to show with the HTTP protocol, it would also have to be absolute.
<!---->
<a href="../welcome.asp">Back to the Welcome Page.</a>
Generally, it is not a good idea to encrypt every single page request with SSL. It makes for slower page serves and more bandwidth usage. It is also more intensive on the server's CPU; something your hosting provider may not be pleased with.
A Solution
Being forced to use absolute URLs for internal links in a web site is less than appealing. The next thing you know, the web site's domain name changes (for any number of reasons) or you have a staging server, which means you have to maintain a separate copy of the site for that set of absolute URLs. It makes much more sense to mark certain files and/or entire directories as "secure". This would allow you the benefit of using relative URLs freely within your web pages. If an existing page needs to be made secure, you simply add it to the list of marked files instead of finding and replacing all links to the page with an absolute URL.
That's where SecureWebPageModule
comes in. SecureWebPageModule
is a class that implements the IHttpModule
interface. HTTP modules give programmers a means of "attaching" to a web application to process its events. It's like descending from the System.Web.HttpApplication
class and overriding the application and session events in a Global.asax file. The main difference is you don't have to worry about copying and pasting the same code into the file for every application that is to use it. HTTP modules are simply "linked in" to a web application and become part of the pipeline.
The goal of this security solution is to allow a developer to easily secure a web site without the need to hard-code absolute URLs. This is accomplished by listing the files and/or directories that should be secured by SSL. It only seems natural to have a custom configuration section for this.
Configuration
Simply add a new section to the web.config file of the application to secure. Make sure you add this section outside of the <system.web>
section but inside the <configuration>
tags. The <secureWebPages>
section has one optional attribute: enabled
. Include the enabled
attribute with a value of true
or false
. The default value is true
which enables the automatic web page security module. Setting this attribute to false
indicates that you want the feature disabled. This is ideal for testing on a machine that doesn't have a certificate installed (like your development machine).
="1.0" ="utf-8"
<configuration>
...
<secureWebPages enabled="true">
...
</secureWebPages>
...
<system.web>
...
</system.web>
</configuration>
Next, you need to specify any files and directories to include for automatic security. Files are added via <file>
tags. Directories are added with <directory>
tags. Both tags require a path
attribute. The path
attribute should be a relative path to the file or directory that will be processed by the security module. In addition, both tags may include an optional attribute: ignore
. Set the ignore
attribute to either true
or false
. The default value is false
, signifying that the file or directory should be secured. If you set this attribute to true
, the security module will not change a request for that file or a file in that directory.
What does that mean? All application requests are processed by the SecureWebPageModule
, when enabled. If the requested file is specified in a <file>
tag or it is in a directory that is specified in a <directory>
tag of the <secureWebPages>
section, it is served to the client securely. That is, the response is redirected to the requested file with the HTTPS protocol if it is not already requested as such. If a request does not have a matching entry in the <secureWebPages>
section, the response is redirected to the requested file with the HTTP protocol if it was not requested as such. The only exceptions are requests that are matched with a <file>
or <directory>
tag that has its ignore
attribute set to true
. In that case, no protocol switch is performed.
...
<secureWebPages enabled="true">
<file path="Login.aspx" />
<file path="Lib/PopupCalendar.aspx" ignore="True" />
<file path="Members/ViewStatistics.aspx" />
<directory path="Admin" />
<directory path="Members/Secure" />
</secureWebPages>
...
The above example enables the SecureWebPageModule
that is linked to the accompanying web application. As a visitor clicks links and maneuvers through the web site, the SecureWebPageModule
processes each request. If the user requests a file located in the Admin or Members/Secure directories, the request is automatically served with the HTTPS protocol. A request to any file in the root directory, except for Login.aspx, is served via the HTTP protocol regardless of the previous request. Files in the Members directory, other than Members/ViewStatistics.aspx, use HTTP. In the case of Login.aspx and Members/ViewStatistics.aspx, HTTPS is used. Finally, if a client requests the PopupCalendar.aspx file in the Lib directory, the file is served exactly as it was requested.
The developer may have chosen to ignore the Lib/PopupCalendar.aspx file because that page is used by several other pages that have calendar icons which, when clicked, opens a pop-up window to the page, allowing the user to select a date. If one of these icons is clicked on a page that is not secure, the developer wouldn't want the Lib/PopupCalendar.aspx page to be served with SSL. On the other hand, if a calendar icon was clicked on a page in the Admin directory, the developer certainly would not want the Lib/PopupCalendar.aspx page to be presented via HTTP. The user would, most likely, be alerted by their browser that they were leaving a secure area to view the pop-up page. I think I can safely state that most users would lose any feeling of comfort after seeing a warning like that.
The SecureWebPageSectionHandler Class
The SecureWebPageSectionHandler
class is extremely important to the SecureWebPageModule
class. SecureWebPageModule
relies on this class to handle parsing of the <secureWebPages>
configuration section in your application's web.config file. This class implements IConfigurationSectionHandler
. It provides an implementation of the Create
method. This method is implemented by handlers to parse the XML of the configuration section. Please download the source code to see all of the classes used in this solution.
public object Create(object parent, object configContext,
XmlNode section)
{
SecureWebPageSettings Settings = new SecureWebPageSettings();
if (section.Attributes["enabled"] != null)
{
Settings.Enabled = (section.Attributes["enabled"].Value.ToLower()
!= "false");
}
if (Settings.Enabled)
{
SecureWebPageCollection SecurePathList;
string Path;
bool Ignore;
foreach (XmlNode Item in section.ChildNodes)
{
if (Item.NodeType == System.Xml.XmlNodeType.Comment)
continue;
else if (Item.Name.ToLower() == "directory")
SecurePathList = Settings.SecureDirectories;
else if (Item.Name.ToLower() == "file")
SecurePathList = Settings.SecureFiles;
else
throw new SecureWebPageSectionException(string.Format(
"'{0}' is not an acceptable setting.",
Item.Name), Item);
if (Item.Attributes["path"] != null &&
Item.Attributes["path"].Value.Trim().Length > 0)
{
Path = Item.Attributes["path"].Value.Trim();
if (Path.Length > 1)
{
if (!Path.StartsWith("/"))
Path = "/" + Path;
if (SecurePathList == Settings.SecureDirectories
&& !Path.EndsWith("/"))
Path += "/";
}
if (Item.Attributes["ignore"] != null)
Ignore = (Item.Attributes[
"ignore"].Value.Trim().ToLower() == "true");
else
Ignore = false;
SecurePathList.Add(new SecureWebPageItem(Path, Ignore));
}
else
throw new SecureWebPageSectionException(
"'path' attribute not found.", Item);
}
}
return Settings;
}
This method parses the XML of the <secureWebPages>
section and stores the information in an instance of the SecureWebPageSettings
class. The SecureWebPageSettings
class contains properties for the enabled
attribute and collections for the directories and files to secure as specified in the configuration section. Each item of the collections is represented by the SecureWebPageItem
class, which defines properties for the path
and ignore
attributes of each <file>
and <directory>
tag.
The SecureWebPageModule Class
The SecureWebPageModule
class is an implementation of the IHttpModule
interface. IHttpModule
defines two methods that must be implemented. The first is the Dispose
method. No resources are being used in this implementation; therefore, the method is empty.
public void Dispose()
{
}
The second method is the Init
method. This method is implemented to initialize the module. In this case, it "hooks" into the application's BeginRequest
event.
public void Init(HttpApplication Application)
{
Application.BeginRequest += (new EventHandler(
this.Application_BeginRequest));
}
Finally, the BeginRequest
event handler processes each request of the application.
private void Application_BeginRequest(Object source, EventArgs e)
{
SecureWebPageSettings Settings =
(SecureWebPageSettings)ConfigurationSettings.GetConfig(
"secureWebPages");
if (Settings != null && Settings.Enabled)
{
HttpApplication Application = (HttpApplication)source;
string RelativeFilePath =
Application.Request.Url.AbsolutePath.Remove(
0,
Application.Request.ApplicationPath.Length).ToLower();
if (!RelativeFilePath.StartsWith("/"))
RelativeFilePath = "/" + RelativeFilePath;
bool MakeSecure = false;
bool Ignore = false;
int i = Settings.SecureFiles.IndexOf(RelativeFilePath);
if (i >= 0)
{
MakeSecure = true;
Ignore = Settings.SecureFiles[i].Ignore;
}
i = 0;
while (!MakeSecure && i < Settings.SecureDirectories.Count)
{
MakeSecure = RelativeFilePath.StartsWith(
Settings.SecureDirectories[i].Path.ToLower());
Ignore = Settings.SecureDirectories[i].Ignore;
i++;
}
if (!Ignore)
{
if (MakeSecure)
SSLHelper.RequestSecurePage();
else
SSLHelper.RequestUnsecurePage();
}
}
}
The event handler retrieves the SecureWebPageSettings
from the configuration settings. These settings are used to determine a course of action. If security is enabled, the application is used to build a relative path for the currently requested file. Next, the SecureFiles
collection of the Settings
object is searched for a match with the relative path of the current request. If no matching file was found, the SecureDirectories
collection is traversed for a path that matches any parent directory of the current request. Lastly, the Ignore
property of the matching item, if any, is tested. If no indication to ignore was present, the request is made as specified. The SSLHelper
class is used to secure or "unsecure" a page request. The helper class simply replaces the current request's protocol and redirects the response, if needed.
Adding the Module to an Application
In order for a HTTP module to work with a web application, it must be added to the list of modules used. The machine.config file includes many modules for you. It's up to you to add any additional modules that you want to process your application(s). There are two ways you can add the module.
The first option you have, is to add the module to an individual application. This requires that you edit the web.config file of the application. You will need to add a custom configuration section handler for the <secureWebPages>
section and a module addition to the <httpModules>
section.
="1.0" ="utf-8"
<configuration>
...
<configSections>
...
<section
name="secureWebPages"
type="Hyper.Web.Security.SecureWebPageSectionHandler,
WebPageSecurity"
allowLocation="false" />
</configSections>
...
<system.web>
...
<httpModules>
...
<add
name="SecureWebPage"
type="Hyper.Web.Security.SecureWebPageModule,
WebPageSecurity" />
</httpModules>
...
</system.web>
...
</configuration>
Your second option is to add the module to all web applications. You will need to make similar modifications to the machine.config file. Editing the machine.config file should only be performed by a knowledgeable person with "Administrator" privileges. Always make a backup of your machine.config file before editing it. If you choose to add the module and configuration section handler to your machine.config file, you should sign the assembly with a strong name and register it in the Global Assembly Cache (GAC). The AssemblyInfo.cs file provided with the project source should have a line near the bottom, that is commented to prevent signing the assembly. To sign the assembly during a compile, un-comment this line.
[assembly: AssemblyKeyFile(@"Key.snk")]
For more information on registering an assembly in the GAC, please refer to the .NET Framework documentation.
Points of Interest
This module was desperately needed for a project I was working on earlier in the year. The web site contained nearly 100 pages and controls in 7 main directories. Some of the directories under the root contained directories as well. It was going to be a nightmare to secure certain areas and pages with absolute URLs.
The ignore
attribute and feature was not in my original design and implementation. Not until I implemented the module in the web site, did I see the need for it. The example I provided above, with the PopupCalendar.aspx page is exactly how I stumbled across the problem.