Click here to Skip to main content
15,867,835 members
Articles / Web Development / ASP.NET
Article

Switching Between HTTP and HTTPS Automatically

Rate me:
Please Sign up or sign in to vote.
4.91/5 (109 votes)
7 Jul 2005BSD10 min read 727K   8.4K   199   202
An article on automatically switching between HTTP and HTTPS protocols without hard-coding absolute URLs.

Automatic Protocol Switching

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.

HTML
<!--
The following will actually be translated as 
https://www.codeproject.com/welcome.asp;
thus, retaining the HTTPS protocol that was last used.
-->
<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).

XML
<?xml version="1.0" encoding="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.

XML
...
<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.

C#
public object Create(object parent, object configContext,
   XmlNode section)
{
    // Create a SecureWebPageSettings object for the settings
    // in this section
    SecureWebPageSettings Settings = new SecureWebPageSettings();

    // Get the enabled attribute
    if (section.Attributes["enabled"] != null)
    {
        Settings.Enabled = (section.Attributes["enabled"].Value.ToLower()
               != "false");
    }

    if (Settings.Enabled)
    {
        // Traverse the child nodes
        SecureWebPageCollection SecurePathList;
        string Path;
        bool Ignore;
        foreach (XmlNode Item in section.ChildNodes)
        {
            if (Item.NodeType == System.Xml.XmlNodeType.Comment)
                // Skip comment nodes (thanks to dcbrower
                // on CodeProject for pointing this out)
                continue;
            else if (Item.Name.ToLower() == "directory")
                // This is a directory path node
                SecurePathList = Settings.SecureDirectories;
            else if (Item.Name.ToLower() == "file")
                // This is a file path node
                SecurePathList = Settings.SecureFiles;
            else
                // Throw an exception for this unrecognized node
                throw new SecureWebPageSectionException(string.Format(
                    "'{0}' is not an acceptable setting.",
                    Item.Name), Item);

            // Get the path attribute value
            if (Item.Attributes["path"] != null &&
                Item.Attributes["path"].Value.Trim().Length > 0)
            {
                // Get the value of the path attribute
                Path = Item.Attributes["path"].Value.Trim();

                // Add leading and trailing "/" characters where needed
                if (Path.Length > 1)
                {
                    if (!Path.StartsWith("/"))
                        Path = "/" + Path;
                    if (SecurePathList == Settings.SecureDirectories
                         && !Path.EndsWith("/"))
                        Path += "/";
                }

                // Check for an ignore attribute
                if (Item.Attributes["ignore"] != null)
                    Ignore = (Item.Attributes[
                       "ignore"].Value.Trim().ToLower() == "true");
                else
                    Ignore = false;

                // Add the item to the collection
                SecurePathList.Add(new SecureWebPageItem(Path, Ignore));
            }
            else
                // Throw an exception for the missing Path attribute
                throw new SecureWebPageSectionException(
                   "'path' attribute not found.", Item);
        }
    }

    // Return the settings
    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.

C#
public void Dispose()
{
    // No resources were used.
}

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.

C#
public void Init(HttpApplication Application)
{
    // Add a reference to the private Application_BeginRequest
    // handler to the application's BeginRequest event
    Application.BeginRequest += (new EventHandler(
         this.Application_BeginRequest));
}

Finally, the BeginRequest event handler processes each request of the application.

C#
private void Application_BeginRequest(Object source, EventArgs e)
{
    // Get the settings for the secureWebPages section
    SecureWebPageSettings Settings =
        (SecureWebPageSettings)ConfigurationSettings.GetConfig(
             "secureWebPages");

    if (Settings != null && Settings.Enabled)
    {
        // Cast the source as an HttpApplication instance
        HttpApplication Application = (HttpApplication)source;

        // Get the relative file path of the current
        // request from the application root
        string RelativeFilePath =
            Application.Request.Url.AbsolutePath.Remove(
            0,
            Application.Request.ApplicationPath.Length).ToLower();
        if (!RelativeFilePath.StartsWith("/"))
            // Add a leading "/"
            RelativeFilePath = "/" + RelativeFilePath;

        // Intialize the flags
        bool MakeSecure = false;
        bool Ignore = false;

        // Determine if there is a matching file path for
        // the current request
        int i = Settings.SecureFiles.IndexOf(RelativeFilePath);
        if (i >= 0)
        {
            MakeSecure = true;
            Ignore = Settings.SecureFiles[i].Ignore;
        }

        // Try to find a matching directory path, if no file was found
        i = 0;
        while (!MakeSecure && i < Settings.SecureDirectories.Count)
        {
            MakeSecure = RelativeFilePath.StartsWith(
                Settings.SecureDirectories[i].Path.ToLower());
            Ignore = Settings.SecureDirectories[i].Ignore;
            i++;
        }

        // Test for the ignore flag
        if (!Ignore)
        {
            // Request a secure/unsecure page as needed
            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.

XML
<?xml version="1.0" encoding="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.

C#
[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.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Web Developer
United States United States
I began programming on my Commodore 64 at around the age of 12. After migrating to DOS and then Windows, I decided to take on the Web. Several languages and platforms later, I have settled in with .NET nicely. I am currently the owner of a software consulting company and lead application developer for a learning-based technology consultation company.

The love of a finished application is usually at war with the desire to improve it as soon as it's released (they're never really finished).

Comments and Discussions

 
GeneralRe: Security Alert Appears Pin
Matt Sollars9-Mar-04 4:02
Matt Sollars9-Mar-04 4:02 
GeneralRe: Security Alert Appears Pin
PranKalam9-Mar-04 8:20
PranKalam9-Mar-04 8:20 
GeneralRe: Security Alert Appears Pin
Matt Sollars9-Mar-04 8:30
Matt Sollars9-Mar-04 8:30 
Generallogin is user control in default.aspx calling a webservice Pin
fetcher9-Feb-04 13:27
fetcher9-Feb-04 13:27 
GeneralRe: login is user control in default.aspx calling a webservice Pin
Matt Sollars10-Feb-04 4:37
Matt Sollars10-Feb-04 4:37 
GeneralAnother Parse Error Pin
RadioButton20-Jan-04 12:12
RadioButton20-Jan-04 12:12 
GeneralRe: Another Parse Error Pin
Matt Sollars3-Feb-04 4:50
Matt Sollars3-Feb-04 4:50 
GeneralGreat Code, one question though Pin
iampud14-Jan-04 15:21
iampud14-Jan-04 15:21 
GeneralRe: Great Code, one question though Pin
Matt Sollars15-Jan-04 4:02
Matt Sollars15-Jan-04 4:02 
GeneralParser Error Pin
Ben Williams17-Dec-03 10:19
Ben Williams17-Dec-03 10:19 
GeneralRe: Parser Error Pin
Matt Sollars17-Dec-03 12:46
Matt Sollars17-Dec-03 12:46 
GeneralRe: Parser Error Pin
Ben Williams18-Dec-03 3:36
Ben Williams18-Dec-03 3:36 
GeneralArticle is excelent!However for those of us who haven't... Pin
Rado Brat16-Dec-03 3:25
sussRado Brat16-Dec-03 3:25 
GeneralRe: Article is excelent!However for those of us who haven't... Pin
Matt Sollars16-Dec-03 6:00
Matt Sollars16-Dec-03 6:00 
GeneralWell done! Pin
Ivo Ivanov11-Dec-03 18:07
Ivo Ivanov11-Dec-03 18:07 
GeneralRe: Well done! Pin
Matt Sollars12-Dec-03 5:10
Matt Sollars12-Dec-03 5:10 
GeneralCool. Pin
Diego Mijelshon5-Dec-03 8:12
Diego Mijelshon5-Dec-03 8:12 
GeneralRe: Cool. Pin
Matt Sollars5-Dec-03 8:39
Matt Sollars5-Dec-03 8:39 
GeneralArticle Corrections Pin
Matt Sollars4-Dec-03 3:11
Matt Sollars4-Dec-03 3:11 
QuestionCode in VB? Pin
alexcalder3-Dec-03 22:36
alexcalder3-Dec-03 22:36 
AnswerRe: Code in VB? Pin
Matt Sollars4-Dec-03 3:04
Matt Sollars4-Dec-03 3:04 
AnswerRe: Code in VB? Pin
Matt Sollars4-Dec-03 5:47
Matt Sollars4-Dec-03 5:47 
AnswerRe: Code in VB? Pin
webooth9-Dec-03 4:32
webooth9-Dec-03 4:32 
GeneralThanks a lot, and a tiny addition Pin
dcbrower2-Dec-03 2:50
dcbrower2-Dec-03 2:50 
GeneralRe: Thanks a lot, and a tiny addition Pin
Matt Sollars2-Dec-03 3:55
Matt Sollars2-Dec-03 3:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.