Integrating Twitter Authentication with Rails
I’m working on a project called Piggy Back, a simple service where friends can keep track of debts between one another. To encourage user registration, I’m lowering the barriers, allowing users to sign up their Twitter and Facebook accounts. I’d like share with you how I got this functionality into this Rails app. A shout out to Chris Powers and Jeff Talbot for their feedback along the way.
Eliminating Plugin Magic
I needed to get my hands into the authentication code if I was going to allow Twitter and Facebook integration. I was using Authlogic and could have extended it with other plugins to give me this support, but I wanted the process for my users to be as seamless as possible. I knew this would probably involve some Piggy Back specific customizations.
Furthermore, I was actually only using about 20% of the functionality Authlogic provides. I had “magic columns” (last_logged_in_at
, failed_login_count
, etc.) because they were free, but I didn’t actually need them.
Finally, making it possible for users to sign up with their Twitter accounts meant a user record with no email or password, fields Authlogic required. There were likely workarounds available, but that meant hacking Authlogic, not working on my app.
Hand Rolling Authentication
Authentication isn’t complicated. I liberated the heavy lifting cryptography algorithms from Authlogic. Obviously, it was important for me to use the exact same algorithm in my own system. Then I got inspired with Jamis Buck’s Bucketwise project and prepped my user to be authenticated.
Authlogic encourages you to use a UserSession
model which lends itself to restful design, but I found it much easier to just create two actions on my SiteController
that simply responded to GET
requests. Originally, I had used require_authentication
as a before_filter
, so I ported those over to the new system as well. Check it all out in the gist.
Now, Let’s Get Twitter Up in There!
Twitter supports OAuth authentication. It’s a little trickier to implement than basic HTTP authentication, but isn’t terribly complicated. Your application, the consumer, gets a couple tokens, which you exchange for request tokens. Then you use the request tokens to do what you will with Twitter, including obtaining access tokens to, well, access users accounts.
There’s a lot that OAuth does and I wasn’t prepared to start from scratch, so I installed the oauth Ruby gem and included it into my application. I also added 4 columns to my users table to store the Twitter information, twitter_id
, twitter_handle
, twitter_token
, and twitter_secret
. My user validation rules adapted to this change to, permitting users without emails or passwords in favor of these attributes.
You need two actions to support Twitter OAuth. One to start the request and the other, known as the callback, to finish it. In examples, these actions are nestled in among other controllers, but I decided for clarity to create a standalone TwitterController
.
It’s a bit too large to embed here, but you can still take a look at the TwitterController
.
The trickiest part was actually making the workflow seamless for all the users. I’m referencing comments in the TwitterController:
- A new users comes to Piggy Back and creates an account by logging in to Twitter. (C)
- An existing user comes back to Piggy Back and logs in via Twitter, but:
- Piggy Back doesn’t now about their Twitter account and the two must be linked (B) or
- Piggy Back knows about their Twitter account and logs them in (A).
To get this split behavior, I simply altered the callback URL that Twitter uses to return flow back to the application. This was simple enough with two named routes:
To handle the case where accounts may be linked, I store the users Twitter attributes in session for a moment, while I redirect to my SiteController#login
action. Here, the user is asked to login specifically to link their account to Twitter. Jeff Talbot offered great feedback about this workflow, suggesting that anywhere there’s a “Sign in with Twitter” link, it should just seamless create a users account. This creates a problem for existing users, however, where they could easily end up with two accounts. To solve this, I’m adding a big button to this login page, “Don’t Have an Account, Create One Instantly.”
Conclusion
So many web apps need to do exactly what I spent Monday doing, so hopefully you can reuse some of the strategies I implemented here.
Chris Powers asked if I thought this could be pulled out into a plugin, and I’m sure the answer is yes. However, it’s not a lot of code and there’s a lot of customizations your app will need within the areas I commented out. I’m not a control freak (I did, after all, convert from C# and ASP.net), but I do believe there is value in controlling your apps code.