Devise + OmniAuth + Facebook API Hacks
While using Devise + OmniAuth for a new Rails 3 project, I got really curious about how to reuse OmniAuth’s strategy classes to fetch additional API data after a user has authenticated.
A lot of people probably use libraries like FbGraph along with OmniAuth, but I wanted something extremely lightweight without duplicating code already in OmniAuth.
I mostly followed Ryan Bates’ railscasts on setting up Devise + OmniAuth.
If you open the oa-oauth gem, you’ll see a lib/omniauth/strategies folder filled with all sorts of OAuth2 providers like Facebook, Twitter, and LinkedIn. Each of these provider classes has most if not everything needed to make direct API calls once an OAuth2 access_token is obtained.
The basic idea is to store the access_token during an OAuth2 login process (Ryan covers this in his railscast) and later on use the token to fetch more data. To do this, you use the access_token to instantiate an OAuth2::AccessToken along with a Facebook specific OAuth2::Client. The Facebook client really just sets the site URL variable so the AccessToken knows where to send requests. It seems simple enough to just use OmniAuth’s strategy classes for the OAuth2::Client. Then I don’t have to maintain provider specific code myself.
I dug around in the Devise, OmniAuth, and OAuth2 code for hours. Long story short, the simplest solution is to just write your own provider client class instead of trying to reuse OmniAuth’s. Bummer. But way easier in the end. OmniAuth is a rack application designed for authentication only. It’s just too convoluted to try to reuse its strategy classes for additional API calls.
Here’s the simple solution I ended up writing…
Create lib/facebook.rb in your rails app
module OAuth2
module Clients
class Facebook < Client
def initialize(client_id, client_secret, opts = {})
opts[:site] = 'https://graph.facebook.com/'
super(client_id, client_secret, opts)
end
end
end
end
Now you can use the Facebook client along with a user specific access_token (stored in a database during the OmniAuth login process) to fetch additional API data.
require 'facebook'
client = OAuth2::Clients::Facebook.new('', '')
token = OAuth2::AccessToken.new(client, access_token)
token.get('/me/friends')
Note that you don’t need to set the client_id, and client_secret when creating a new Facebook object. The id and secret are only needed to initially obtain the access_token which you already have.
On a side note, it’s good to keep in mind that every additional OmniAuth provider creates a new rack application which gets called on every request. Hopefully this will not be the case in future versions so that an arbitrary number of providers can be supported without any performance concerns.
links for 2011-01-29 « sySolution
Jan 29, 2011 @ 15:00:42
[...] Devise + OmniAuth + Facebook API Hacks « Simple10 (tags: rails facebook api) [...]
Mar 03, 2011 @ 15:17:43
Hi Joe,
As I’m a newbee on rails and heroku, do you have any good tutorial or sample project to use facebook API with rails3 and heroku ? Thanks for your help ;-)
Apr 24, 2011 @ 20:13:54
Great job boiling down what is needed for this – I was just about to write a ‘rest_client’ facebook lib for my app but this seems to be a better approach leveraging what should be there already.
What are the required versions for all of the components to get this working?
When I try this – I get:
>> token.get(‘/me/friends’)
NoMethodError: You have a nil object when you didn’t expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.empty?
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/net/http.rb:1470:in `initialize’
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/net/http.rb:1588:in `initialize’
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/net/http.rb:772:in `new’
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/net/http.rb:772:in `get’
from /Library/Ruby/Gems/1.8/gems/faraday-0.6.1/lib/faraday/adapter/net_http.rb:49:in `call’
from /Library/Ruby/Gems/1.8/gems/faraday-0.6.1/lib/faraday/request/url_encoded.rb:14:in `call’
from /Library/Ruby/Gems/1.8/gems/faraday-0.6.1/lib/faraday/request.rb:88:in `run’
from /Library/Ruby/Gems/1.8/gems/faraday-0.6.1/lib/faraday/request.rb:28:in `run’
from /Library/Ruby/Gems/1.8/gems/faraday-0.6.1/lib/faraday/connection.rb:170:in `run_request’
from /Library/Ruby/Gems/1.8/gems/oauth2-0.2.0/lib/oauth2/client.rb:47:in `request’
from /Library/Ruby/Gems/1.8/gems/oauth2-0.2.0/lib/oauth2/access_token.rb:32:in `request’
from /Library/Ruby/Gems/1.8/gems/oauth2-0.2.0/lib/oauth2/access_token.rb:36:in `get’
from (irb):40
Jun 29, 2011 @ 18:54:20
Thanks for this post, your lib worked great! One quick note is that your sample code sets opts[:site] to a full html anhor tag where I think you just wanted it to be the URL. I changed that in my version and it worked just fine.
Nov 09, 2011 @ 07:07:18
That’s really a shame – seems like little modification to omniauth could make this API accessor available across the board. Any change w/ version 1.0?