Register | Login

Stacking Code

public interface IBlog { string Dump(Stream consciousness); }

Speed Bumps

Tuesday, 7 December, 2010 @ 10:41 PM < Adam Boddington
Tags: ASP.NET, Building Neno, DotNetOpenAuth

This is post #14 in the Building Neno series. Please click here for a description of the Building Neno project and instructions on how to access the source code for this post.

Last night I experienced my first two speed bumps in building Neno.

DotNetOpenAuth and URL Rewriting

The first occurred after I moved the new OpenID implementation to my hosting server. At first glance, it worked okay. I could log in... but then I noticed that the URL of my site was incorrect. It was http://stackingcode.com/stackingcode.com/ instead of http://stackingcode.com/. Digging around it became clear that the problem was happening on the redirect back from the OpenID provider. Was the return URL given to the OpenID provider wrong?

Before I delve into that, let me explain my hosting setup. I'm cheap. I'm extraordinarily cheap. I have a few domain names from ventures gone by -- none of them run anything very big. So when I look for hosting, I typically go for the ones that allow multiple domains. Or at least, allow multiple domain names to point to it. With the incredibly easy URL rewriting in IIS 7, its simplicity itself to set up a subdirectory for each site and redirect based on the host header. So I set it up that way, with http://stackingcode.com/ redirecting to a stackingcode.com subdirectory. So when http://stackingcode.com/stackingcode.com/ starts showing up in the address bar, I can't help but think my cheapness is about to bite me in the @$$.

Not one to RTFM, I went straight to the DotNetOpenAuth source code. It turns out if a realm and return URL aren't provided to the request, they're calculated automatically. And yeah, for reasons I don't really fathom (I'm not a security expert), they're generated by looking at the URL before any rewriting has taken place. Bugger.

Despite the pain, there was a little joy to be had -- and that was in the wonderful goodness of open source. I love that I was able to find this in the amount of time it took to download the code for DotNetOpenAuth and open up the OpenIdRelyingParty class. About three minutes.

Being a cookie that thinks he's smart, I immediately coded a request where I specified the realm and return URL explicitly.

//IAuthenticationRequest request = openIdRelyingParty.CreateRequest(identifier);

// DotNetOpenAuth uses URL before rewriting, which is causing problems with the
// way stackingcode.com is hosted. Set the realm and return to url manually.

// Getting a full URL can be done by specifying the protocol.
var realm = new Realm(Url.Action("index", "home", null, Request.Url.Scheme));
var returnToUrl = new Uri(Url.Action("authenticate", "user", new { ReturnUrl = returnUrl }, Request.Url.Scheme));
IAuthenticationRequest request = openIdRelyingParty.CreateRequest(identifier, realm, returnToUrl);

Unfortunately that threw up a new error in DotNetOpenAuth itself.

The openid.return_to parameter (http://stackingcode.com/...) does not match the actual URL (http://stackingcode.com/stackingcode.com/...) the request was made with.

Hmm, I can't fool DotNetOpenAuth. There's probably another security reason why it makes that check so I'm just going to have to find another way.

Time to google and luckily I happened across a note on the DotNetOpenAuth site about domain level URL rewriting. (RTFMing probably would have helped me find it sooner.)

It turns out I need to fake out the other side of the comparison as well. I need to set up a fake HttpRequest and pass that into the OpenIdRelyingParty response. No problem, I can do that.

var openIdRelyingParty = new OpenIdRelyingParty();
//IAuthenticationResponse response = openIdRelyingParty.GetResponse();

// DotNetOpenAuth uses URL before rewriting, which is causing problems with the
// way stackingcode.com is hosted. Fake the request manually.
// http://sean-lynch.net/dotnetopenauth-with-appdirectory-removed/

IAuthenticationResponse response;

if (Request.ApplicationPath == "/")
    response = openIdRelyingParty.GetResponse();
else
{
    var absoluteUri = new UriBuilder(Request.Url.AbsoluteUri);
    absoluteUri.Path = absoluteUri.Path.Replace(Request.ApplicationPath, string.Empty);
    string rawUrl = Request.RawUrl.Replace(Request.ApplicationPath, string.Empty);
    var headers = new WebHeaderCollection();

    foreach (string header in Request.Headers)
        headers.Add(header, Request.Headers[header]);

    var requestInfo = new HttpRequestInfo(Request.HttpMethod, absoluteUri.Uri, rawUrl, headers, Request.InputStream);
    response = openIdRelyingParty.GetResponse(requestInfo);
}

Booyah, it all started working again, in both development and production. I don't like that I've got some pretty cryptic code to work around a not uncommon hosting scenario -- but I'm happy it's working at least.

For a few minutes anyway. Which brings me to the second speed bump.

Random Logouts

If I logged into my site and navigated around for a while, eventually my login would be "lost", as evidenced by the sudden appearance of "Register | Login" in the top right hand corner. There was no rhyme or reason to it -- it didn't matter which page I went to, or how long I surfed for. Sometimes it would happen straight after logging in. Sometimes it would happen a few minutes later. A check of my cookies revealed the .ASPXAUTH cookie was still present in my browser and not set to expire for another 48 hours. Well if the .ASPXAUTH cookie is there, why isn't it showing me as logged in?

This took me ages to figure out -- and this is partly why I'm building this blog engine -- to get more experience with building web applications for the actual web. You see, in corporate intranet land, I don't have to worry about things like forms authentication. Or encryption and validation keys. Or recycling application pools. From that list you may have guessed what was doing me in, but I had no idea until I noticed the only pattern to my login disappearing -- and that was the server was taking longer to serve up the page than it normally did right before the login vanished.

Googling put me on to the concept of application pool recycling. And a quick browse of my hoster's forums found a handful of customers recently complaining about frequent application pool recycles -- like every one or two minutes. Still, why would that affect me? Isn't the forms authentication cookie persisted on the client side -- shouldn't that save it across session resets and application pool recycles? "Normally, yes," the internet said, "but you're an ignorant corporate intranet lacky who has forgotten everything he ever learnt about forms authentication years ago. Forms authentication cookies are encrypted with a key that is automatically generated when a web application starts up. If the web application is rebooted, the key changes, and all the cookies out there in the wild become immediately worthless." Oh crap, bells are ringing all over the place.

The short and long of it is... Set the encryption and validation keys in the Web.config manually -- don't let them be automatically generated. That way the forms authentication cookies will continue to work even after the application pool recycles -- or IIS reboots -- or the server itself reboots.

Lessons learnt. Back to building actual features.

There are 0 comments.


Comments

Leave a Comment

Please register or login to leave a comment.


Older
A Simple OpenID Selector

Newer
User Profile

Older
A Simple OpenID Selector

Newer
User Profile

browse with Pivot


About


Projects

Building Neno


RSS
Recent Posts

Codility Nitrogenium Challenge
OS X Lock
HACT '13
Codility Challenges
Priority Queue


Tags

Architecture (13)
ASP.NET (2)
ASP.NET MVC (13)
Brisbane Flood (1)
Building Neno (38)
C# (4)
Challenges (3)
Collections (1)
Communicator (1)
Concurrency Control (2)
Configuration (1)
CSS (5)
DataAnnotations (2)
Database (1)
DotNetOpenAuth (2)
Entity Framework (1)
FluentNHibernate (2)
Inversion of Control (5)
JavaScript (1)
jQuery (4)
Kata (2)
Linq (7)
Markdown (4)
Mercurial (5)
NHibernate (20)
Ninject (2)
OpenID (3)
OS X (1)
Pivot (6)
PowerShell (8)
Prettify (2)
RSS (1)
Spring (3)
SQL Server (5)
T-SQL (2)
Validation (2)
Vim (1)
Visual Studio (2)
Windows Forms (3)
Windows Service (1)


Archives


Powered by Neno, ASP.NET MVC, NHibernate, and small furry mammals. Copyright 2010 - 2011 Adam Boddington.
Version 1.0 Alpha (d9e7e4b68c07), Build Date Sunday, 30 January, 2011 @ 11:37 AM