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.

Comments
ryancacophony@gmail.com

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@stakeventures.com

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

ryancacophony@gmail.com

Ryan Clough July 23rd, 2009

Thanks a bunch!

brian@morearty.org

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@stakeventures.com

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@stakeventures.com

Pelle Braendgaard October 8th, 2009

Just released 0.3.14 which includes Brians fix.

britt.v.crawford@gmail.com

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.v.crawford@gmail.com

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.net

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.

philsonite@googlemail.com

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@toppingdesign.com

Topper Bowers August 27th, 2010

I’m also getting the undefined method ‘login_required’ problem.

maik@mediavrog.net

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.de

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]

rog_21@yahoo.com

Roger February 14th, 2011

I am running into this issue:

undefined method `callback_oauth_consumer_url’ for #

Any ideas?

aidan.feldman@gmail.com

Aidan Feldman March 1st, 2011

@Roger You are probably missing the route… add this to config/routes/rb:

resources :oauth_consumers do
  get :callback, :on => :member
end
lists@josf.se

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?

lists@josf.se

Josef Ottosson March 8th, 2011

Fixed it, the solution seems to have been to install portablecontacts.

rockycaam@gmail.com

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@polkaspots.com

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

mkins45@yahoo.com

Joyeria Contemporanea July 20th, 2011

The solution seems to have been to install portablecontacts!!

daniel@danielz.nl

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?

About me

Pelle gravatar 160

My name is Pelle Braendgaard. Pronounce it like Pelé the footballer (no relation). CEO of Notabene where we are building FATF Crypto Travel Rule compliance software.

Most new articles by me are posted on our blog about Crypto markets, regulation and compliance

More about me:

Current projects and startups:

Other under Programming

Popular articles

Topics: