This project is now being maintained on GitHub under the new name “Security Switch”. All updates will be posted and tracked there. This article will remain here for educational purposes, but all issues should be reported at its new location. A new version of this library is also available from NuGet:
PM> Install-Package SecuritySwitch
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.
See the What's New section for the latest updates.
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 that 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 website 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
="1.0"="utf-8"
<configuration>
...
<secureWebPages
mode="RemoteOnly"
encryptedUri="secure.mysite.com"
unencryptedUri="www.mysite.com"
maintainPath="True"
warningBypassMode="AlwaysBypass"
bypassQueryParamName="BypassSecurityWarning"
ignoreHandlers="WithStandardExtensions">
...
</secureWebPages>
...
<system.web>
...
</system.web>
</configuration>
Attribute and Possible Values | Description |
mode
On (Default) | Security is enabled and all requests are monitored. | RemoteOnly | Only requests from remote clients are monitored. | LocalOnly | Only requests from the local server are monitored. | Off | No requests are monitored. |
| The mode determines when the module will monitor requests for redirection. |
encryptedUri | Set to a specific URI to indicate where to redirect when the module decides that security is needed. |
unencryptedUri | Set to a specific URI to indicate where to redirect when the module decides that security is not needed. |
maintainPath
True (Default) | When redirecting to the specified URIs (encryptedUri and unencryptedUri ), the current path is maintained. | False | Prevents the module from maintaining the current path when redirecting to the specified URIs (encryptedUri and unencryptedUri ). |
| This flag indicates whether or not the module should maintain the current path when redirecting to encryptedUri or unencryptedUri . |
warningBypassMode
AlwaysBypass | Always bypass security warnings when switching to an unencrypted page. | BypassWithQueryParam (Default) | Only bypass security warnings when switching to an unencrypted page if the proper query parameter is present. | NeverBypass | Never bypass security warnings when switching to an unencrypted page. |
| Use this attribute to bypass any security warnings that may be displayed. |
bypassQueryParamName | Set to the name of a query parameter that will indicate to the module to bypass any security warning if warningBypassMode = "BypassWithQueryParam ". The default value is "BypassSecurityWarning ". |
ignoreHandlers
BuiltIn (Default) | The built-in HTTP handlers should be ignored. Currently, these are Trace.axd and WebResource.axd. | WithStandardExtensions | All files that have an extension that corresponds to standard HTTP handlers should be ignored. Currently, that is *.axd files. | None | No HTTP handlers should be ignored unless specifically specified in the files or directories entries. |
| This attribute is used to instruct the module to ignore HTTP handlers. This is only used in version 3.x and above. |
mode
behaves like the customErrors mode
attribute. Use it to avoid redirecting to the HTTPS protocol on your development or production machine when you don't have a secure certificate installed.
The encryptedUri
and unencryptedUri
attributes are perfect for those situations where your secure certificate is associated with a different site than the one needing security. I know; examples are better than just explaining it. Okay then, visit Sears Mastercard and notice the security alert that appears. If you are using Internet Explorer, you should see a security alert window appear, warning you that something is wrong with the site's certificate. Everything is okay except the name doesn't match the name of the site. Click on the View Certificate button and note that the site listed next to "Issued to." "www.searscard.com" is the site they purchased the certificate for.
What does this mean? Well, the site should be www.searscard.com. However, the IT folks at Sears thought it would be a good idea to purchase the "searsmastercard.com" domain as well as allow for another point of entry. Both DNS records point to the same place on their Web server. They have a few options that would prevent this alert from displaying to their users, but two are the most obvious. They can redirect to https://www.searscard.com when users visit the default page on www.searsmastercard.com or they could upgrade their site to ASP.NET and download this module. All they'd have to do then is set the encryptedUri
attribute to www.searscard.com and all would be wonderful.
Likewise, unencryptedUri
may be specified to send the user back to another domain or specific URI when the module removes security. An example would be to redirect secure requests to secure.mysite.com and requests that don't need to be secure could be redirected back to www.mysite.com. maintainPath
is used in conjunction with the aforementioned attributes. When the module redirects to the encryptedUri
or unencryptedUri
, it appends the current path before sending users on their way. You can turn off this behavior by setting maintainPath
to False
.
In certain circumstances, Internet Explorer displays the message, "You are about to be redirected to a connection that is not secure." This only happens as a result of a "double redirect" to an unsecured page. That is, when a page is requested via HTTPS, the programmer's code performs a relative redirect and then the module performs an absolute redirect via the HTTP protocol.
Use the warningBypassMode
attribute to bypass the security warning as desired. If you choose to bypass security warnings via the default BypassWithQueryParam
option, you may specify the name of the query parameter with the bypassQueryParamName
attribute. Use this when you only want to run the bypass code on the rare occasions when you know the security warning will appear, and request the page with the query parameter specified. An example of this is when your code will be posting back to a secure page for server-side processing and then redirecting to a page that the module will deem unsecured. Simply redirect to the page and append the bypassQueryParamName
with any value like this:
Response.Redirect("MyUnsecurePage.aspx?BypassSecurityWarning=True");
I received a couple of suggestions on how to solve the above warning. There was one suggestion that involved a configuration attribute that would point the module to a "redirector page." This page would be sent a parameter containing the page that should be redirected to and it would change the location via meta refresh and JavaScript as a backup. The idea is a good one. I just don't like making the user of this module create a page that has preset code in it. Therefore, if the module determines that it should bypass the warning, it will render the necessary page itself, complete with meta tag and JavaScript. This will cause a client-side redirect and avoid the security warning.
One power of ASP.NET is the ability to create custom HTTP handlers that act similarly to this module. The handlers are invoked when a certain file or type of file is requested from the server. In ASP.NET 2.0, embedded resources make heavy use of the WebResource.axd virtual file to dynamically serve images and JavaScript that don't actually have a physical file. When used, these handlers may cause mixed security warnings unless the module is instructed to ignore them. The ignoreHandlers
attribute lets you generally ignore these handlers quite easily. You may configure the module to ignore any standard HTTP handler with a file extension of *.axd by setting the attribute to WithStandardExtensions
. The default setting is BuiltIn
and forces the module to just ignore the two built-in handlers Trace.axd and WebResource.axd.
Now... on to the file
and directory
entries.
secureWebPages for .NET 1.1
...
<secureWebPages>
<file path="Default.aspx" secure="Insecure" />
<file path="Admin/MoreAdminStuff.aspx" secure="Insecure" />
<file path="Legal/Copyright.aspx" secure="Ignore" />
<file path="Lib/PopupCalendar.aspx" secure="Ignore" />
<directory path="/" recurse="False" />
<directory path="Admin" />
<directory path="Admin/Info" secure="Insecure" />
<directory path="Members/Secure" recurse="True" />
</secureWebPages>
...
secureWebPages for .NET 2.0
...
<secureWebPages>
<files>
<add path="Default.aspx" secure="Insecure" />
<add path="Admin/MoreAdminStuff.aspx" secure="Ignore" />
<add path="Legal/Copyright.aspx" secure="Ignore" />
<add path="Lib/PopupCalendar.aspx" secure="Ignore" />
</files>
<directories>
<add path="/" recurse="False" />
<add path="Admin" />
<add path="Admin/Info" secure="Insecure" />
<add path="Members/Secure" recurse="True" />
</directories>
</secureWebPages>
...
Notice that you can now include the application root as a directory
entry. There is no longer an ignore
attribute for each entry. It has been replaced by the secure
attribute. This attribute tells the module how to handle that particular file or directory. The default value is Secure
, which simply means that the module should redirect to the HTTPS protocol when that file or a file in that directory is requested.
Setting the attribute to Insecure
will force a matching request to be served without SSL. In the example above, although the application root is secured, the Default.aspx page in the application root should not be. The secure
attribute may also have a value of Ignore
, which mimics the functionality of version 1's ignore
attribute. Any request to a file with a matching file
or directory
entry marked with secure="Ignore"
will be ignored by the module. This is good when a page should remain in the same protocol as the last request, such as pop-up windows used by both secure and unsecured pages.
Another example of this is an ASPX page that is used to serve content other than HTML and is referenced from within a secure page. There are times when an ASPX page will serve an image or a style sheet and is included by a secure page with the appropriate <img>
or <link>
tag. In these cases, a visitor of such a page would receive a warning that there is a mixture of secure and unsecured items. That usually doesn't make users feel too good about a page. Just include an entry for the file and set secure
to Ignore
, and there will be no problems.
You may also provide the recurse
attribute for directory
entries. Setting this attribute to True
will inform the module to include all files in any sub-directories when monitoring requests.
Adding the Module to Applications
There are two options for adding the module to your applications. The first 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.
configSections for .NET 1.1
="1.0"="utf-8"
<configuration>
...
<configSections>
...
<section
name="secureWebPages"
type="Ventaur.Web.Security.SecureWebPageSectionHandler,
WebPageSecurity"
allowLocation="false" />
</configSections>
...
</configuration>
configSections for .NET 2.0
="1.0"
<configuration>
...
<configSections>
...
<section
name="secureWebPages"
type=
"Ventaur.Web.Security.Configuration.SecureWebPageSettings,
WebPageSecurity" />
</configSections>
...
</configuration>
httpModules on IIS 6.x and Earlier or IIS 7 in "Classic" Mode
="1.0"="utf-8"
<system.web>
...
<httpModules>
...
<add
name="WebPageSecurity"
type="Ventaur.Web.Security.SecureWebPageModule,
WebPageSecurity" />
</httpModules>
...
</system.web>
...
</configuration>
modules on IIS 7.x and Later in "Integrated" Mode
="1.0"="utf-8"
<system.webServer>
...
<modules>
...
<add
name="WebPageSecurity"
type="Ventaur.Web.Security.SecureWebPageModule,
WebPageSecurity"
preCondition="managedHandler" />
</modules>
...
</system.webServer>
...
</configuration>
The second option is to add the module to all Web applications on the server. 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 handlers 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.
IIS 7
With the appearance of IIS 7, an "Integrated" mode is now available that integrates the IIS pipeline with our Web applications. This requires a new configuration approach as shown above. I recommend using the preCondition
attribute when adding this module so it will only be instantiated for typical ASP.NET resources (like *.aspx, *.ashx, etc.). Those of you that want the module to process all requests, including static files (like *.htm, *.jpg, *.css, etc.), leave this pre-condition out, but be aware that you may have to add a bit of extra file/directory path entries to this module's configuration section in order to get the desired results you want. For example, if you are getting warnings about mixed secure and non-secure content on a page that you designate as secured, check for images and style sheets that are being forced to use HTTP. In those cases, add file
and/or directory
entries that tell the module to ignore these resources when processing.
Notes
Please be aware that although IIS allows you to "Require a secure channel (SSL)" for a folder's "Directory Security," this module will not work properly if you do so. IIS will intercept the request before passing it along to the module and reject insecure connections. Therefore, if you want to use this module, you do not require SSL from IIS.
Also, testing this module on a development machine without an installed SSL certificate will yield unexpected results. The browser may appear to "hang" or fail altogether. This is because it is being sent to a page that should be encrypted, but is not.
You can get rid of the warnings regarding missing schema information and get IntelliSense for the <secureWebPages>
configuration section by following a few easy steps:
- Copy the appropriate schema file (Ventaur.WebPageSecurity.v3x.xsd or Ventaur.WebPageSecurity.v2x.xsd) into the root of your
WebPageSecurity
source code (or the folder of your liking; maybe a common schema folder on your computer). - Open up a website that is using this module in Visual Studio (or the IDE of your choice that supports IntelliSense).
- Open the web.config file for the website.
- Click "Schemas" from the "XML" menu or click the ellipsis button next to "Schemas" in the Properties window for the web.config file.
- Click the "Add..." button in the XML Schemas window that appears.
- Browse to the schema file you copied in step 1 and select it (open it).
- Make sure it is in the list and has a green check next to it in the first column, labeled "Use". If not, click in that column next to it (target namespace is "http://Ventaur/WebPageSecurity.v3x.xsd"), and choose "Use this schema" from the drop-down list.
- Click the "OK" button to close the dialog.
- Finally, if you already have a
secureWebPages
section defined in your configuration, add this attribute to the tag, xmlns="http://Ventaur/WebPageSecurity.v3x.xsd"
(substitute v2x for v3x if you are using version 2.x of this module).
Your configuration section for this module should then look like this:
<secureWebPages xmlns="http://Ventaur/WebPageSecurity.v3x.xsd" mode="RemoteOnly" ...>
...
</secureWebPages>
Version History
- Version 3.1
- Version 2.6
- Version 3.0
- Version 2.5
- Bug fix:
encryptedUri
/unencryptedUri
domain differing from the current request domain. For example, if unencryptedUri="www.mysite.com"
, and the user visits a page via mysite.com, 4 characters (www.
) were dropped when the path was maintained during a redirect. This bug was squashed - See what's new for more details
- Version 2.1
- Changed the
maintainApplicationPath
attribute to maintainPath
- Improved the code that redirects to another host via the
encryptedUri
and unencryptedUri
- Version 2.0
- Version 1.0.1
- Added code to skip comments in the configuration file. Thanks goes to dcbrowser
- Minor performance changes to select areas of the code
- Version 1.0.0
Download file for binaries (all versions) last updated on 11th December, 2008.
Version 3.1.4 and 2.6.4
- Added a schema file for the
<secureWebPages>
configuration section to offer IntelliSense and no warnings when building websites that use the module. - Made sure that the '
xmlns
' attribute is allowed by the configuration section code.
Version 3.1.3 and 2.6.3
- Moved the event handler that processes the request from the application's
BeginRequest
event to the PostAcquireRequestState
event. This ensures that the session ID is available for cookie-less session processing.
Version 3.1.2 and 2.6.2
Version 3.1
- Added the
ignoreHandlers
attribute to generally ignore HTTP handlers. I normally do not like setting the default of a new setting to something that is different from the previous version's default functionality, but I believe it is acceptable in this case. The default setting is BuiltIn
to force the module to ignore requests for Trace.axd and WebResource.axd - The module now accounts for cookie-less sessions when redirecting without a specified
encryptedUri
or unencryptedUri
- A tiny change was made to the
RequestEvaluator.Evaluate
method that allows you to force an evaluation, despite the mode
setting in the configuration. This should allow for better testing and simulation
Version 3.0
- Ported to .NET 2.0
- Configuration re-written to conform to the new configuration API
Version 2.6
- The module now accounts for cookie-less sessions when redirecting without a specified
encryptedUri
or unencryptedUri
. - A tiny change was made to the
RequestEvaluator.Evaluate
method that allows you to force an evaluation, despite the mode
setting in the configuration. This should allow for better testing and simulation.
Version 2.5
- Separated appropriate logic into
static
helper classes. This adds benefit for testing purposes. Now, simple testing can be achieved by simulating the processes. - Removed the configuration restriction of "order matters" for directory elements. Now, directory elements may be included in the configuration in any order. The module processes directory matches with the attitude that the deepest match is the best match. In other words, if a request is made for a page located in Admin/Reports/ and the configuration file has a directory element for both Admin/ and Admin/Reports/, it no longer matters if Admin/ is first. Before, the module would stop once it found a match, despite recursion settings. Now, it finds the most accurate (deepest) match. Thanks goes out to balazs_hideghety for getting me started with this feature update.
Version 2.0 and 2.1
- Source code provided in C# and VB.NET
- The configuration settings loader is now called once in an application
Init
event handler, and if the settings indicate not to use security, a handler for the BeginRequest
event is not added. This improves the overall performance of the module and any application including it. Thanks goes to Diego - The root directory of the virtual application can now be secured by including a
directory
tag with a path of "/". Thanks to Andy for pointing this one out - A
mode
attribute was added to the secureWebPages
section. Possible values are On
, RemoteOnly
, LocalOnly
or Off
. Thanks to Andy for this suggestion - The
encryptedUri
and unencryptedUri
attributes were added to the secureWebPages
section. Set to a specific URI to indicate where the module should redirect when it decides whether security is needed or not. Thanks to Andy for this suggestion - The
maintainPath
attribute was added to the secureWebPages
section. A value of False
will prevent the module from maintaining the current path when redirecting to any specified URIs - The
warningBypassMode
attribute was added to the secureWebPages
section. Possible values are AlwaysBypass
, NeverBypass
or BypassWithQueryParam
- The
bypassQueryParamName
attribute was added to the secureWebPages
section. Set the value to the name of a query string parameter that indicates to the module to bypass any security warning if warningBypassMode
is set to BypassWithQueryParam
- The
ignore
attribute was removed from the file
and directory
tags - The
secure
attribute was added to the file
and directory
tags. Possible values are True
, False
or Ignore
directory
tags may include the new recurse
attribute. If True
, all files in any sub-directories are included
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).