Consuming OAuth intelligently in Rails
Published July 21st, 2009 edit replace rm!
It has been fairly easy to provide OAuth services in your web application (see How to turn your Rails Site into an OAuth Provider), but to actually consume Twitter, FireEagle, my own Agree2 and other OAuth services has been a fairly manual affair.
There are great gems out there that wrap up the process for the above mentioned services. So it hasn’t been too hard to support one of them. But what to do if you want to support 5 different services today and more in the future?
I knew there should be some generic approach to handle the OAuth authorization process, but had not spent too much time thinking about it until we actually needed to consume external web services for Agree2.
Well I think I’ve cracked it in a a nice Dont Repeat Yourself fashion.
OAuth Consumer Generator
I have now added a oauth_consumer generator to the Rails OAuth Plugin.
The oauth_consumer generator creates a controller to manage the authentication flow between your application and any number of external OAuth secured applications that you wish to connect to.
To run it simply run:
./script/generate oauth_consumer
This generates the OauthConsumerController as well as the ConsumerToken model.
OAuth Authentication Flow
For each service you support the consumer presents the following 2 urls:
- http://yoursite.com/oauth_consumers/twitter
- http://yoursite.com/oauth_consumers/twitter/callback
To connect to a users twitter account just add a link somewhere to go to the first URL and the plugin takes care of the rest. The callback is used internally and you don’t really need to worry about it.
If you send the user back the first URL he/she will have the option of removing or reconnecting. A http DELETE at that URL will remove an existing token.
Configuring services
You can configure any amount of services to support by editing:
All configuration of applications is done in
config/initializers/oauth_consumers.rb
Add entries to OAUTH_CREDENTIALS for all OAuth Applications you wish to connect to. Get this information by registering your application at the particular applications developer page. I decided to use Ruby rather than yaml here so heroku users can Config Values here.
OAUTH_CREDENTIALS={
:twitter=>{
:key=>"key",
:secret=>"secret"
},
:fireeagle=>{
:key=>"key",
:secret=>"ssh"
},
:agree2=>{
:key=>"key",
:secret=>"secret"
},
:hour_feed=>{
:key=>"bb",
:secret=>"aa",
:options={
:site=>"http://hourfeed.com"
}
}
}
For generic OAuth services you need to add key, secret and an options hash with information of which URLs to use. See OAuth::Consumer for valid values. For services with wrapper classess (I’ll get into those shortly) you only need to specify key and secret as a wrapper class handles the rest.
For every service you add here you get the before mentioned 2 urls. So to link to Agree2 in the above example have a user follow a link to http://yoursite.com/oauth_consumers/agree2.
Using the OAuth Services
Every time a user is linked to one of these services it creates a new ConsumerToken. It actually uses STI and I dynamically create a new Subclass of ConsumerToken for each service listed in OAUTH_CREDENTIALS.
Using the above example you get 3 new classes:
- TwitterToken
- FireeagleToken
- Agree2Token
- HourFeedToken
This makes it easy to use from your user object. Add the following in your user model:
has_one :twitter_token,:class_name=>"TwitterToken", :dependent=>:destroy
has_one :fire_eagle,:class_name=>"FireEagleToken", :dependent=>:destroy
has_one :agree2,:class_name=>"Agree2Token", :dependent=>:destroy
has_one :hour_feed,:class_name=>"HourFeedToken", :dependent=>:destroy
Now you can access each of these directly in your own code. Each of these tokens implement a client method which will allow you to do stuff on the web service
current_user.hour_feed.client.get("/report")
By default this is an instance of OAuth::AccessToken but you can write wrapper classes to use a higher level library.
Wrapper Tokens
Wrapper tokens are classes written using to create a high level approach to working with a service such as Twitter. The plugin comes with wrapper classes for Twitter, Agree2 and FireEagle but it is fairly easy to write your own.
Here is an example using Twitter. It uses the Twitter Gem to provide a high level api:
current_user.twitter.client.update("Just had fantastic Jerk Pork on Boston Beach. Need beer!!")
This example uses FireEagle. It uses the Fireagle Gem to provide a high level api:
@location=current_user.fireeagle.location
Creating your own wrapper tokens
If you have a service in CONSUMER_CREDENTIALS called :my_service just add a class to your models folder called MyServiceToken and it will be used instead of the auto generated one. In this example we are imagining a gem called ‘myservice’ that wraps around the web services actual api.
# Use a gem to do the heavy stuff
require 'myservice'
class MyServiceToken < ConsumerToken
MY_SERVICE_SETTINGS={:site=>"http://my_service.com"}
# override self.consumer so we don't have to specify url's in options
def self.consumer
@consumer||=OAuth::Consumer.new credentials[:key],credentials[:secret],MY_SERVICE_SETTINGS
end
# overide client to return a high level client object
def client
@client||=MyService::Client.new( MyServiceToken.consumer.key,MyServiceToken.consumer.secret, token, secret)
end
# optionally add various often used methods here
def shout(message)
client.shout(message)
end
end
If there isn’t a wrapper gem for the service you want to use. You can always add methods to MyServiceToken to do the parsing etc.
I hope this will get you started implementing OAuth. There no longer is an excuse not to.
Do you need advise or help with implementing OAuth in your Rails application?
I am available as a consultant and would be glad to help your company out. Whether you need help in developing an OAuth strategy or in practical hands on implementation help. Send me an email at [email protected] to let me know what you need help with and I can give you an estimate and my rate.
Ryan Clough July 22nd, 2009
So weird you posted this literally 16 minutes before I was looking for how to do it, but I noticed a dependency issue- oauth-plugin uses oauth 3.5 and twitter uses 3.4; how would you resolve this/ I’m pretty new with ruby :/
Pelle Braendgaard July 22nd, 2009
Hi Ryan,
You should install Paul Singh’s version of the Twitter gem. He just bumped the OAuth version number and there aren’t any other changes.
sudo gem install paulsingh-twitter —source http://gems.github.com
Ryan Clough July 23rd, 2009
Thanks a bunch!
Brian Morearty October 8th, 2009
Hi,
This tutorial is great. Thanks.
One thing that took me a few hours to figure out, though: you wrote “If you have a service in CONSUMER_CREDENTIALS called :my_service just add a class to your models folder called MyServiceToken and it will be used instead of the auto generated one.”
But it doesn’t work—the oauth plugin never looks for a subclass in the models folder.
I’ve just created a fork at http://github.com/BMorearty/oauth-plugin that fixes this problem. I’ve sent you a pull request.
Thanks for the great gem.
Pelle Braendgaard October 8th, 2009
Thanks Brian,
I was just hit by this myself and was going to start investigating it.
I’ll have a look at your fix.
P
Pelle Braendgaard October 8th, 2009
Just released 0.3.14 which includes Brians fix.
Britt October 25th, 2009
Is there somewhere else I can view the tutorial? The continue reading link just links back to this abbreviated version.
Britt October 25th, 2009
@pelle Disregard my last comment. I checked backa few minutes later to see if you’d responded and everything was fine. That’s what I get for using Google Chrome for OS X.
Manoel Lemos November 27th, 2009
Hi @pelle, I really need your help with a roadblock…. in my app, user accounts are represented by a model called Account (and all related methods does the same).
How can I make oauth-plugin to handle that instead of User ?
Thanks in advance and congrats for such useful post and plugin.
phil December 15th, 2009
Thanx for that great resources (gem and articles).
I would be interested, how you configure the oauth consumer to be able to login via one of the consumers e.g. with twitter.
in your approach you need a current_user to have access to the oauth_consumer_token.
thanx in advance
phil
greg August 22nd, 2010
I’m trying to implement an OAuth consumer and after following the instructions above, I keep getting this error message:
NoMethodError in Oauth consumersController#show
undefined method `login_required’
Any ideas on what could be going wrong? I googled around and saw people running into the issue when creating OAuth providers, but couldn’t find similar cases/fixes for an OAuth consumer.
Topper Bowers August 27th, 2010
I’m also getting the undefined method ‘login_required’ problem.
mediavrog September 21st, 2010
Hi Pelle,
thanks for your great work bringing oAuth to Rails in a convenient fashion. There’s one thing i’m not quite sure about i want to ask you: Will this plugin support oAuth 2.0? I know, all the basic features are in your oAuth Gem (including support for oAuth 2.0) and i think of oAuth-Plugin to be a convenience wrapper for even easier handling – is this right?
Thanks in advance for your help.
Kind Reards,
Maik
Stephan Eisler November 29th, 2010
Is there a difference between development and production mode?
http://localhost:3000/oauth/request_token
works perfect, but
http://waldstat.com/oauth/request_token
don’t works with the same client code. ( https://github.com/Eisler/WaldstatOauth-Java-Example )
production.log:
Processing OauthController#request_token (for 79.220.200.220 at 2010-11-28 11:17:13) [POST]
Filter chain halted as [#<OAuth::Controllers::ApplicationControllerMethods::Filter:0×7fd37fc67f50 @options={:interactive=>false, :strategies=>:two_legged}, @strategies=[:two_legged]>] rendered_or_redirected.
Completed in 1ms (View: 0, DB: 0) | 401 Unauthorized [http://waldstat.com/oauth/request_token]
Roger February 14th, 2011
I am running into this issue:
undefined method `callback_oauth_consumer_url’ for #
Any ideas?
Aidan Feldman March 1st, 2011
@Roger You are probably missing the route… add this to config/routes/rb:
Josef Ottosson March 8th, 2011
I seem to have issues with the service token not being available for some reason (Although it’s probably in the noob category, being very new to RoR). From I http://0.0.0.0:3001/oauth_consumers/twitter I get
NameError (uninitialized constant TwitterToken):
/var/lib/gems/1.8/gems/oauth-plugin-0.3.14/lib/oauth/controllers/consumer_controller.rb:70:in `load_consumer’
Is the class not created on the fly as described? Or am I losing it somewhere along the way?
Josef Ottosson March 8th, 2011
Fixed it, the solution seems to have been to install portablecontacts.
Rocky March 25th, 2011
I’m trying to implement an OAuth consumer but I keep getting this error message:
NoMethodError in Oauth consumersController#show
undefined method `new__session_path’ for #
Any ideas on what could be going wrong?
Simon M May 2nd, 2011
Hi,
I have this working OK although I’m getting the following error:
uninitialized constant Twitter::OAuth
Is there something I can do to sort this – have looked all over for the solution..
Simon
Joyeria Contemporanea July 20th, 2011
The solution seems to have been to install portablecontacts!!
DanielZ July 25th, 2011
I followed your github readme. Created an provider app and a consumer app. but i cant get it to work. When i try to connect to the provider from the consumer app i get a 401 error. I really dont know how to fix this any tips?