- Posted by miketeye on April 19, 2007
Share on Facebook The other day I ran into a very unusual situation while solving a routine site down problem. I say unusual because you would'nt get into that situation if things go well. That said, when does it ever go according to plan? If you are in Engineering, IT or any technical discipline, I expect you already are well acquanted with that FACT.
Well, I discovered by after a little google research that, the version of the domain we use for our e-commerce site (the www prefixed version), had less "presence" and weight in the google database compared to the non-www version. There was also a higher page rank on the non-www version compared to the www version in use.
The immediate difficulty is the fact that, the site is an e-commerce site and has an SSL certificate issued by a publicly recognised Certificate Authourity for a year and PAID FOR! This in effect means if we have to salvage the page rank on the domain name variant to our advantage then we have to serve the pages under that domain. The consequence of that however will be warning messages by most browsers to our potential customers that the SSL certificate in use on the site is suspicious, which means loss in sales.
So I set out to find a WIN-WIN solution. I know that google penalises duplicate content so we must avoid serving same content under the different variants of the domain. Google has addressed this one step in their sitemaps system of the google webmaster tools. However, on the canonicalisation front we are still expected to serve content under only one canonical form of the url.
At this point I figured out why not try to serve all the http pages which are public and can be submitted to search engines on the strongly ranked non-www version of the domain and still keep the same SSL certificate for serving secure content under the www version of the domain.
There are quite a number of Url rewritting modules for ASP.NET out there, one of which I have used before and allows you to force a site to run either on the www or non-www version of the domain permanently and site-wide.
This useful tool was from Ewal.net. It issued a 301 redirect back to the client and redirected to whichever version of the domain is set as the preferred version. But then again, this will not solve the problem because it will affect all content site-wide, both secure and unsecure content, which is not what we want.
Then I remembered using a tool I downloaded sometime ago from the Code Project which was called WebPageSecurity written by Matt Solars and which can be obtained with necessary demo and documentation via this link. At the time I first used it, I understood it could switch between domains for secure and unsecure content. I however remember not getting that aspect to work much as I tried. Since I had need again, I tried and it worked the magic this time around. There are two main settings in web.config for the module that need setting: The optional encryptedUri and unencryptedUri elements of the main <webPageSecurity mode="On"> setting. Set the encryptedUri to the www version of the domain and set the unencryptedUri to the non-www version. You can swap the settings the other way round to suite your case.
I immediately discovered that the module was not forcing the switch to the appropriate domain in cases where it was already secure or unsecure.
ie. Say you have a request formed as https://non-wwwdomain.com/xyz.aspx
and submitted to the server, the module as is from the download will not substitute the non-wwwdomain in the example with the wwwdomain. The good thing is the download included the source code so I set out to fix the bug.
I used the VB version of the project so for the purpose of this post I will restrict coverage to that version. However, I think it should be pretty easy to figure out the corresponding lines in the C# version. Open the WebPageSecurity-VB version of the project with Visual Studio 2005. You should find a file named SSLHelper.vb. In that file, you need to find two methods of the SSLHelper class and modify them.
The first of the 2 methods is the Function "DetermineSecurePage"
Replace the original function code with the slightly modified version below:
Public Shared Function DetermineSecurePage(ByVal settings As SecureWebPageSettings, ByVal ignoreCurrentProtocol As Boolean) As String
Dim Result As String = Nothing
Dim Request As HttpRequest = HttpContext.Current.Request
' Is this request already secure?
Dim RequestPath As String = Request.Url.AbsoluteUri
If ignoreCurrentProtocol OrElse RequestPath.StartsWith(UnsecureProtocolPrefix) Then
' Is there a different URI to redirect to?
If String.IsNullOrEmpty(settings.EncryptedUri) Then
' Replace the protocol of the requested URL with "https".
' * Account for cookieless sessions by applying the application modifier.
Result = String.Concat( _
SecureProtocolPrefix, _
Request.Url.Authority, _
HttpContext.Current.Response.ApplyAppPathModifier(Request.Path), _
Request.Url.Query _
)
Else
' Build the URL with the "https" protocol.
Result = BuildUrl(True, settings.MaintainPath, settings.EncryptedUri, settings.UnencryptedUri)
End If
ElseIf Request.Url.Host <> settings.EncryptedUri Then
Result = String.Concat( _
SecureProtocolPrefix, _
settings.EncryptedUri.ToString, _
HttpContext.Current.Response.ApplyAppPathModifier(Request.Path), _
Request.Url.Query _
)
End If
Return Result
End Function
The 2nd very similar method is the Function DetermineUnsecurePage
Replace the original function body with the listing below:
Public Shared Function DetermineUnsecurePage(ByVal settings As SecureWebPageSettings, ByVal ignoreCurrentProtocol As Boolean) As String
Dim Result As String = Nothing
Dim Request As HttpRequest = HttpContext.Current.Request
' Is this request secure?
Dim RequestPath As String = HttpContext.Current.Request.Url.AbsoluteUri
If ignoreCurrentProtocol OrElse RequestPath.StartsWith(SecureProtocolPrefix) Then
' Is there a different URI to redirect to?
If String.IsNullOrEmpty(settings.UnencryptedUri) Then
' Replace the protocol of the requested URL with "http".
' * Account for cookieless sessions by applying the application modifier.
Result = String.Concat( _
UnsecureProtocolPrefix, _
Request.Url.Authority, _
HttpContext.Current.Response.ApplyAppPathModifier(Request.Path), _
Request.Url.Query _
)
Else
' Build the URL with the "http" protocol.
Result = BuildUrl(False, settings.MaintainPath, settings.EncryptedUri, settings.UnencryptedUri)
End If
End If
Return Result
End Function
With these modifications build the project and use the dll in your project as per the sample and instructions from the download and the switching should be nice and smooth from secure (https) domain to unsecure (http) domain.
It wasn't long before I discovered the next and final setback. Once a user logged in under the secure version of the domain, they appear not to be logged in under the unsecure version. This problem arises because ASP.Net cookies are issued per the exact version of the domain or subdomain used during login by default. The good news is, it is possible to modify the domain during login so as to make it a little tolerant to slight variations in the domain, such as subdomains etc. I found a wonderful tutorial on Single Sign-On (SSO) forms authentication which helped me cross this final barrier at
Hernan de Lahitte's blog. Another article which explain's this cookie domain manipulation further was written by Altair valasek and can be reached at
http://www.codeproject.com/aspnet/aspnetsinglesignon.asp
The actual modification to my site was just an extra line of code each in my login and registration pages to modify the domain property of the HttpCookie object attached to the Response object like so:
Dim cookie As HttpCookie = FormsAuthentication.GetAuthCookie( UserId.Text, false )
cookie.domain = "non-wwwdomain"
Response.AppendCookie(cookie)
One other thing is to manually expire the Cookie at logout. Under normal circumstances, forms authentication can expire the cookie on a call to formsauthentication.Signout, but for domain modified cookies, we have to explicitly expire the cookie by setting the expires property like so:
Dim C As System.Web.HttpCookie=Request.Cookies(System.Web.Security.FormsAuthentication.FormsCookieName
C.Domain = "example.com"
C.Expires = DateTime.Now.AddDays(-1)
Response.Cookies.Add(C)
So eventually, I was able to serve the product, category, promotions, and news pages of the e-commerce site under the preferred version of the domain while keeping the secure pages serve under the version on which the SSL certificate was originally issued. Hope my experiences outline here, although not as detailed as can be, would help someone solve a similar problem. Cheers!