My current ror project at Berkman is a new Herdict website being released later this week. It is an extension of the BadwareBusters.org project that has been released to GitHub as LittleVoice. My work in LittleVoice primarily involves restful authentication and scoring model methods, but for this new project I’ve been tasked with adding Twitter, OpenId and anonymous authentication.
In retrospect, I might have been better off implementing Authlogic. But since I had already implemented Restful Authentication with all the bells and whistles in LittleVoice, it seemed to make more sense to simply throw in the open_id_authentication and the twitter_authentication plugins and be done with it. Mistake #1
Redirecting to OpenID and Twitter via the form_remote_tag form doesn’t work. The app just hangs. I tried various options and incantations of form_remote_tag to no avail. Forunately, I could just bypass this nuisance issue with a simple form_for.
Otherwise, Twitter worked well out of the box except for the fact that the api does not return a user’s email address after authentication for security reasons. This bummed me out because the LittleVoice app has a business rule requiring all users to have a unique email address. But problem easily solved by requesting email up front as part of my authentication form. No big deal.
Open ID however has a number of quirks that caused me great pain to work out. And in researching these quirks and issues, I had a hard time finding information short of digging into the OpenID api. And who wants to dig into the api? I don’t have time for that! Mistake #2
Next, I found that my login and signup pages were working fine. But authenticating via OpenID on the fly didn’t. LittleVoice allows users to write a post before logging in, and rather than “submit” be able to login in-line. But logging in via OpenID in the middle of my controller just wouldn’t work right.
What the heck was different between these two methods? This is a great example of why it pays to be DRY. But I digress…
In my sessions controller, I invoked OpenID in a form_tag that redirected to the open_id_logon named route. In my other controller, I invoked OpenID by directly calling the authenticate_with_open_id plugin method. Mistake #3
What wasn’t apparent right away is the fact that the authenticate_with_open_id method needs to be called twice. This method redirects to the OpenID url entered by the user. When the OpenID website is ready to return back to your site, you need to make sure that it calls authenticate_with_open_id again so you can grab the result, identity_url and registration object. By calling the method directly in another controller, my return_to url was my controller method rather than the authenticate_with_open_id method. Simply using the named route at all times, solves this issue. But there’s another way.
There is a little known option for the authenticate_with_open_id. You can set the return_to directly like this:
authenticate_with_open_id(openid_url, :return_to => open_id_logon_url) do |result, identity_url|
...
end
But here’s the really painful part. When you try to use the return_to option you get an error message.
OpenID::Server::UntrustedReturnURL
There is a bug in the open_id_redirect_url method of the OpenIdAuthentication module. The current module code looks like this:
def open_id_redirect_url(open_id_request, return_to = nil, method = nil)
...
open_id_request.redirect_url(requested_url, return_to || requested_url)
end
But it needs to be this:
def open_id_redirect_url(open_id_request, return_to = nil, method = nil)
...
open_id_request.redirect_url(return_to || requested_url, return_to || requested_url)
end
*sigh* I can’t wait to close this ticket.