Sunday, September 14, 2008


OpenID on Insoshi on Rails 2.1

It's late Sunday night and I'm fixing spaghetti with picante sauce while doing laundry. Who is up for some technical blogging?
Update 10/16 - OpenID support is now in the master branch of Insoshi. If you install Insoshi, you've got it.

Update 1/17/09 - updated Austin on Rails pointer which changed.

Insoshi is open source software built with Ruby on Rails. It lets anyone create their own social networking website. Insoshi is a work in progress but since it is open source, it can be improved by anyone. On August 23, there was a request for OpenID on the Insoshi google group. Tonight I'm going to describe the steps I took to get OpenID working with Insoshi. If you want to try it out, just clone my fork of Insoshi on github, do a git submodule init & update, and follow the installation instructions. Of course, let me know if you have any questions or suggestions.

Insoshi is available under the AGPL. As the Insoshi site explains, the AGPL is like the GNU GPL but fixes the loophole for web applications.

Insoshi uses the restful_authentication plugin, so I decided to use the open_id_authentication plugin since plenty of people have already described their experiences using these two together. Currently, the open_id_authentication plugin on github does not work with Rails 2.1 as it is dependent on changes made in edge rails, so I forked my own copy (including it as a git submodule) and made two changes to get it working with 2.1 again. The first change for 2.1 compatibility was unfixing OpenIdAuthentication module's requested_url method to go back to using the deprecated (for edge rails presumably) request.relative_url_root. The second fix was similarly easy to fix. For this one, I lazily borrowed the solution from another fork instead of writing my own. Fields that are posted by the client when she posts her OpenID need to be plumbed through to the OpenID server so that they are available after redirecting to the OpenID server. What was happening here was that the authenticity token posted from the client was getting lost. We fix this by passing the token to the OpenID server. The authenticity token is what prevents cross site request forgeries (which I described at Austin on Rails last year)

Update 9/15: Josh fixed #1 five minutes ago.

Now that the openid plugin is compatible with Rails 2.1, let's start using it within the existing restful_authentication generated files. For the basics, I'm using the railscast episode approach. First, make sure you have the ruby-openid gem installed.

If you're familiar with restful_authentication, you know it generates a sessions controller and a users controller. Insoshi renames the latter the people controller. Note that in the Railscast, it refers to a login instance variable of User. I have changed this instance variable to name (of Person) to work with Insoshi. For old school logins, the client identifies himself with an email parameter instead of a login parameter and for OpenID, the nickname from the OpenID server is assigned to name instead of login. You can see these basic changes here. Also note that for OpenID logins, I've bypassed the validations associated with passwords in the Person model by changing the password_required? method in the Person model.

Update 9/15: We also need to gracefully prohibit an email login for an account that has an OpenID associated with it. So, we change the lookup in Person.authenticate to
u = find_by_email_and_identity_url(email.downcase.strip,nil)
I like the way slicehost's OpenID login lets you toggle between OpenID and old school so I'm doing it that way too. The javascript I checked in differs from slicehost in that I clear out the openid_url field when the old school form is chosen. This way, when the old school credentials are passed, I can easily detect it is not an OpenID login. (I use slicehost for all my deployments)

So, what if the required fields (email & nickname) provided by the OpenID server do not validate? We need to provide the client the opportunity to manually populate those fields. In this checkin, I save a verified_url cookie variable so that when the required fields are manually submitted, we also have an openid to create the new Person.

Before I cover the "remember me" function, I should mention the demo mode you can put Insoshi into. If you log into Insoshi as admin, you click a checkbox for demo mode. I did this as it prints a nice message "This is a demonstration site. Data may be modified or removed without warning." (You can see this message on Insoshi's demo site) After I did this, I got an IM from Rich telling me he was editing my profile. I proceeded to get very paranoid wondering how I had screwed up. It soon became apparent that demo mode makes person id 2 the demo account which everyone can automatically log into. Of course, I was person #2 (admin is #1).

Rails 2.0 introduced cookie-based sessions by default. This requires a secret key to generate hashes for session variables stored in cookies. Because Insoshi is an open source rails application, another thing to keep in mind is that Insoshi could not simply use the rails app generator to generate a secret key. Each installation will generate its own secret key. Since you will be keeping your keys in the home directory, you will need to create a symbolic link in your deploy.rb to your shared directory so they don't get blown away when you deploy. Obviously, you won't be checking them in to your repository (Btw, you will also need to do this for your public/photos directory for your attachment_fu profile photos or they will also disappear when you deploy.)

The "remember me" check box is delegated to the OpenID server, so for OpenID login, the checkbox on the relying party website should not exist. For instance, when the client logs into a website and he's redirected to his OpenID server, the client will be presented with a checkbox that says, for instance, "Skip this step next time I sign in to" If the client checks that box, then subsequent accesses to will allow the client to proceed immediately without seeing anything from the OpenID server. This will happen if the OpenID server is sent a checkid_immediate mode request. However, the open_id_authentication plugin does not support checkid_immediate. Also, if we modify the plugin to send the checkid_immediate and the client didn't click "skip this step" then the server will send SETUP_NEEDED and the plugin will try to access a setup_url instance variable of the response in complete_open_id_authentication(). First, this is a private variable so it will throw an exception. Second, the setup url is no longer supported in the OpenID 2.0 specification. So, in the next update we'll work around that.

Labels: , , , ,

Thanks for doing this! I hope the Insoshi team adopts it! -Lance
Just signed up for an OpenID. Thanks for the inspiration!
Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?