I've been doing some work this week integrating with OpenID, using Janrain's OpenID library. Scott Hanselman talked about his experience with it a few weeks ago, though I'll admit that frankly, the fact that the library is in Boo didn't really cause me any concerns. I still don't know Boo, really, but the syntax is simple and clear enough that following the code and even attempting a few modifications wasn't really a problem. I do want to take a deeper look at it, though, as it is pretty cool.
But I disgress. This post is not really about Boo, but about actually putting the library to good use to build an OpenID-enabled consumer application. The API for the library is actually pretty straightforward, and takes care of most of the grunt work of implementing a Smart-mode consumer application (as far as I can see the public API doesn't allow you to implement a pure dumb-mode application). However, after fighting a lot with it, I still couldn't get it to work correctly.
Here's what happening: I was pretty quickly able to generate an initial redirect URL for the initial authentication process that would take me to the identity provider's sign on page. In my case, I'm using MyOpenID for testing. That part worked well and I was able to sign in using my openid url. However, the redirect back to my application after signing on was failing: MyOpenID would, at the last step, present an error screen saying that the logon request had an invalid value for the openid.mode. I was using checkid_setup, and I could see that perfectly well in the redirect URL, so surely that wasn't it. Or was it?
After a lot of fiddling around I came to the conclussion that indeed the problem was not the mode argument, but that the redirect URL that Janrain's OpenID library was generating. I started looking at the library code (which is actually very, very readable) and adding a bunch of Debug.WriteLine() statements for tracing.
What I discovered was that the library actually takes great care to ensure that the redirect URLs it creates are wellformed, manually encoding all arguments that are added into the URL QueryString. This is particularly important because with OpenID you send a lot of encoded URLs in the query string.
At last I was able to discover that the URL got mangled in the last step just before it was returned to the application. Well, mangled isn't the right word. What actually was happening was that the arguments in the query string of the generated URL were getting un-escaped, which in turn was causing MyOpenID to choke on them at the end of the authentication process. The last few lines I'm talking about are in the AuthRequest.boo file and are this:
redir = UriBuilder(self.endpoint.ServerUrl)
return Uri(redir.ToString(), true)
Now, Janrain's library was originally written for .NET 1.1 (although the site claims it supports .NET 2.0 as well), but I was running in .NET 2.0. After looking a bit in the documentation, I ran into the the docs for this specific overload of the System.Uri class constructor, which is now deprecated:
Of course, the orgiinal developers of the library knew very well they had already escaped their querystring properly and didn't want the Uri class to mess with it, but since the Uri class in .NET 2.0 simply ignores the dontEscape parameter and goes ahead and escapes it, it ended up breaking things up pretty heavily. It's not that it unescaped it, it was more that it didn't deal very well with embedded URLs in the query string.
Now, I wanted to see if I could work around it without changing anything in the library, and found the promising Uri.EscapeUriString() method. I figured I could try to run the screwed up Uri I got from the library and see if it fixed it. No dice, it didn't deal with the URLs any better. I have yet to found a good way to work around this issue while keeping the use of the Uri class here, so if anyone has any suggestions, I'd sure appreciate them.
For now, I resorted to simply modifying the CreateRedirectUrl() method of the AuthRequest class to return the URL as a string instead of a System.Uri instance, like this:
It's a bit of a hack, and I really didn't want to modify the library, but at least for now it allowed me to successfully pass the authentication process. My work is not yet complete, though, as I'm running into a separate issue with session state that has nothing to do with OpenID at all, but I can work around that in time.
If you're using this library, this might be something to keep in mind!