Does OmniAuth provide simple hooks to the Facebook Graph API? - ruby-on-rails

I am working on integrating Omniauth with my new Facebook application, and I am looking through the rather sparse documentation to understand if it gives simple ways to access the graph API... I am moving from Koala which was pretty simple.
Has anyone out there used Omniauth for this yet? I want to get photos from the users' albums, and sort and get the unique URLs for them.

I finally found out:
1) include this gem
2) use the gem:
user = FbGraph::User.new('me', :access_token => session[:omniauth]["credentials"]["token"])
user.fetch
3) retrieve your information
user.name
Remember you can get anything from here ttp://developers.facebook.com/docs/reference/api/user

Another good option is Koala: https://github.com/arsduo/koala
If you're just using Facebook, Koala has its own OAuth support. It also works fine with OmniAuth. To use them together, set up OmniAuth per this Railscast:
http://railscasts.com/episodes/235-omniauth-part-1
Then add a 'token' column to 'authentications' table, and any supporting methods for retrieving tokens. When the app needs to interact with Facebook, let Koala grab the token and do its thing. In a controller:
if #token = current_user.facebook_token
#graph = Koala::Facebook::GraphAPI.new(#token)
#friends = #graph.get_connections("me", "friends")
end

First, I would go for fb_graph, just compare:
with koala:
graph = Koala::Facebook::GraphAPI.new OAUTH_ACCESS_TOKEN
friends = graph.get_connections("me", "friends")
graph.put_object("me", "feed", :message => "I am writing on my wall!")
with fb_graph:
me = FbGraph::User.me OAUTH_ACCESS_TOKEN
my_friends = me.friends
me.feed! :message => "I am writing on my wall!"
When using omniauth, every user has many authorizations (facebook, twitter, ...)
For each user authorization, you should store the oauth token that is returned in your oauth callback hash.
auth = Authorization.create!(:user => user,
:uid => hash['uid'],
:provider => hash['provider'],
:token => hash['credentials']['token'])
Then wherever you want to access albums and photos, do something like this:
class User
...
has_many :authorizations
...
def grap_facebook_albums
facebook = authorizations.where(:provider => :facebook).first
fb_user = ::FbGraph::User.fetch facebook.uid, :access_token => facebook.token
fb_albums = fb_user.albums
end
...
end

So I wasn't able to get fb_graph to work properly - I am still a noob having been a Ruby On Rails developer for a total of about 8-10 weeks, and therefore don't have an instinct for what must be obvious problems to other folks.
However I found this great little blog post which outlines a simple facebook client and shows clearly how it all plugs together. I found an issue with it picking up the me/picture object as Facebook returns an http302 not http200 but that was easily worked around with the help of the author. Check it out: http://bnerd.de/misc/ruby-write-basic-client-for-facebook-graph-api/
I am now using Omniauth for the simplicity of the login/signup interaction based on this walkthrough here: blog.railsrumble.com/blog/2010/10/08/intridea-omniauth and with the token I get from that I am using this simple FBClient from the bnerd reference above to access the Graph API. Hope what I found helps others.
...here's my version of bnerd's code and it worked for me:
class FBClient
def initialize(app, access_token = nil)
#app = app
#access_token = access_token
end
# request permission(s) from user
def request(perms)
#create a random verifier to identify user on fb callback
verifier = (0...10).map{65.+(rand(25)).chr}.join
uri = "https://graph.facebook.com/oauth/authorize?client_id=#{#app.app_id}&redirect_uri=#{#app.connect_url}?verifier=#{verifier}&scope=#{perms}"
request = { :verifier => verifier, :uri => uri }
return request
end
def connect(code, verifier)
uri = URI.parse("https://graph.facebook.com/oauth/access_token?client_id=#{#app.app_id}&redirect_uri=#{#app.connect_url}?verifier=#{verifier}&client_secret=#{#app.secret}&code=#{CGI::escape(code)}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path + "?" + uri.query)
response = http.request(request)
data = response.body
return data.split("=")[1]
end
# get, post
def get(path, params = nil)
uri = URI.parse("https://graph.facebook.com/" + path)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
if params.nil?
params = Hash.new
end
if params["access_token"].nil?
params["access_token"] = #access_token unless #access_token.nil?
end
request = Net::HTTP::Get.new(uri.path)
request.set_form_data( params )
request = Net::HTTP::Get.new(uri.path + "?" + request.body)
return response = http.request(request)
end
def post(path, params = nil)
uri = URI.parse("https://graph.facebook.com/" + path)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
if params.nil?
params = Hash.new
end
if params[:access_token].nil?
params[:access_token] = #access_token unless #access_token.nil?
end
request = Net::HTTP::Post.new(uri.path)
request.set_form_data( params )
request = Net::HTTP::Post.new(uri.path + "?" + request.body)
response = http.request(request)
response.code == "200" ? feed = JSON.parse(response.body) : raise("Sorry, an error occured. #{response.body}")
return feed
end
end
I am sure someone more experienced than I could improve this - I was about 10 weeks into learning Ruby (and my first programming since Fortran and Pascal at university in the early 90s!).

I also had problems getting the devise+Omniauth solution to work. I had to problems:
The session cookie did not store the facebook authentication token that is necessary to initialize fb_graph and koala.
I was unable to initialize fb_graph after getting the facebook authentication token in place (but got Koala to work after some work).
I solved #1 by inserting 'session[:omniauth] = omniauth' into the create method of the authentications_controller.rb.
I solved #2 by using Koala. Seem like fb_graph requires oauth2, and the devise omniauth integration use oauth. Koala works with perfectly with the facebook authentication token stored by session[:omniauth]["credentials"]["token"].
You initialize koala like this:
- #fbprofile =
Koala::Facebook::GraphAPI.new(
session[:omniauth]["credentials"]["token"]
)

I made sure oauth and oauth2 were uninstalled, and then I installed oauth2. It appears that now omniauth and fb_graph are working ... but probably need to test it more.

Related

401 Unauthorized error with Jawbone Up API and Ruby on Rails

I am trying to access the Jawbone API via Omniauth, HTTParty & Ruby on Rails. I'm passing the header Authorization => Bearer: [token_here] as required by the API docs, but it's still giving me a 401 error and I can't for the life of me figure out why. I'm able to successfully get the token via omniauth and store it to session[:token], but the API returns 401 unauthorized.
API documentation: https://jawbone.com/up/developer/authentication
I've searched stack overflow and couldn't find a solution to this. What's the problem?
My code is:
sessions_controller.rb
def create
auth_hash = request.env['omniauth.auth']
if session[:user_id]
#if there's a session, then create the authorization
User.find(session[:user_id]).add_provider(auth_hash)
else
auth = Authorization.find_or_create(auth_hash)
session[:user_id] = auth.user.id
session[:token] = auth.user.authorizations.first.token
end
redirect_to '/today'
end
data_processor.rb
class DataProcessorController < ApplicationController
def today
#current_user_dp = session[:user_id]
#token_dp = session[:token]
#authorization_dp = "Bearer " + #token_dp
##auth = "Bearer " + session[:current_user].authorizations.first.token unless session[:user_id].nil?
#result = HTTParty.get('http://jawbone.com/nudge/api/v.1.1/users/#me/goals',
:headers => { "Authorization" => #authorization_dp, "Accept" => "application/json"}) unless session[:token].nil?
end
end
results.html.erb
<p>Result: <%= #result %></p>
<p>Auth header: <%= #result.request.inspect%></p>
output of HTML file:
Result: {"meta"=>{"code"=>401, "error_detail"=>"You must be logged in to perform that action", "error_type"=>"authentication_error", "message"=>"Unauthorized"}, "data"=>{}}
Auth header: #<HTTParty::Request:0x007fe446bb3320 #http_method=Net::HTTP::Get, #path=#<URI::HTTPS:0x007fe446bbb408 URL:https://jawbone.com/nudge/api/v.1.1/users/#me/goals>, #options={:limit=>4, :default_params=>{}, :follow_redirects=>true, :parser=>HTTParty::Parser, :connection_adapter=>HTTParty::ConnectionAdapter, :headers=>{"Authorization"=>"Bearer XXXXXXXXXTOKEN_HEREXXXXXXXXXXXXX", "Accept"=>"application/json"}}, #last_uri=#<URI::HTTPS:0x007fe446bba918 URL:https://jawbone.com/nudge/api/v.1.1/users/#me/goals>, #raw_request=#<Net::HTTP::Get GET>, #last_response=#<Net::HTTPUnauthorized 401 Unauthorized readbody=true>, #redirect=true>
Did you solve this?
If you look at the other error fields it may give you more information. In my case I was getting 'unauthorized scope'. I was requesting items in a scope that I hadn't included in my original oauth request so the token I was providing had limited scope.
Try changing the scope when you first authorize user. I used scope=move_read and was able to get data from http://jawbone.com/nudge/api/v.1.1/users/#me/goals.

Refresh token using Omniauth-oauth2 in Rails application

I am using omniauth-oauth2 in rails to authenticate to a site which supports oauth2. After doing the oauth dance, the site gives me the following, which I then persist into the database:
Access Token
Expires_AT (ticks)
Refresh token
Is there an omniauth method to refresh the token automatically after it expires or should I write custom code which to do the same?
If custom code is to be written, is a helper the right place to write the logic?
Omniauth doesn't offer this functionality out of the box so i used the previous answer and another SO answer to write the code in my model User.rb
def refresh_token_if_expired
if token_expired?
response = RestClient.post "#{ENV['DOMAIN']}oauth2/token", :grant_type => 'refresh_token', :refresh_token => self.refresh_token, :client_id => ENV['APP_ID'], :client_secret => ENV['APP_SECRET']
refreshhash = JSON.parse(response.body)
token_will_change!
expiresat_will_change!
self.token = refreshhash['access_token']
self.expiresat = DateTime.now + refreshhash["expires_in"].to_i.seconds
self.save
puts 'Saved'
end
end
def token_expired?
expiry = Time.at(self.expiresat)
return true if expiry < Time.now # expired token, so we should quickly return
token_expires_at = expiry
save if changed?
false # token not expired. :D
end
And before making the API call using the access token, you can call the method like this where current_user is the signed in user.
current_user.refresh_token_if_expired
Make sure to install the rest-client gem and add the require directive require 'rest-client' in the model file. The ENV['DOMAIN'], ENV['APP_ID'] and ENV['APP_SECRET'] are environment variables that can be set in config/environments/production.rb (or development)
In fact, the omniauth-oauth2 gem and its dependency, oauth2, both have some refresh logic built in.
See in https://github.com/intridea/oauth2/blob/master/lib/oauth2/access_token.rb#L80
# Refreshes the current Access Token
#
# #return [AccessToken] a new AccessToken
# #note options should be carried over to the new AccessToken
def refresh!(params = {})
fail('A refresh_token is not available') unless refresh_token
params.merge!(:client_id => #client.id,
:client_secret => #client.secret,
:grant_type => 'refresh_token',
:refresh_token => refresh_token)
new_token = #client.get_token(params)
new_token.options = options
new_token.refresh_token = refresh_token unless new_token.refresh_token
new_token
end
And in https://github.com/intridea/omniauth-oauth2/blob/master/lib/omniauth/strategies/oauth2.rb#L74 :
self.access_token = access_token.refresh! if access_token.expired?
So you may not be able to do it directly with omniauth-oauth2, but you can certainly do something along the lines of this with oauth2:
client = strategy.client # from your omniauth oauth2 strategy
token = OAuth2::AccessToken.from_hash client, record.to_hash
# or
token = OAuth2::AccessToken.new client, token, {expires_at: 123456789, refresh_token: "123"}
token.refresh!
Eero's answer unlocked a path for me to solve this. I have a helper concern for my classes which get me a GmailService. As part of this process, the user object (which contains the google auth info) gets checked if it's expired. If it has, it refreshes before returning the service.
def gmail_service(user)
mail = Google::Apis::GmailV1::GmailService.new
# Is the users token expired?
if user.google_token_expire.to_datetime.past?
oauth = OmniAuth::Strategies::GoogleOauth2.new(
nil, # App - nil seems to be ok?!
"XXXXXXXXXX.apps.googleusercontent.com", # Client ID
"ABC123456" # Client Secret
)
token = OAuth2::AccessToken.new(
oauth.client,
user.google_access_token,
{ refresh_token: user.google_refresh_token }
)
new_token = token.refresh!
if new_token.present?
user.update(
google_access_token: new_token.token,
google_token_expire: Time.at(new_token.expires_at),
google_refresh_token: new_token.refresh_token
)
else
puts("DAMN - DIDN'T WORK!")
end
end
mail.authorization = user.google_access_token
mail
end
There is some information here, too much to list here. It may depend on the provider you are using, and their allowed usage of the refresh-token
Similarly to other answers I followed this approach, where the model storing the auth and refresh tokens is used, abstracting API interactions from that logic.
See https://stackoverflow.com/a/51041855/1392282
If you are using devise you can create a new strategy the following way I guess, so that you don't need to repeat client id and secret everywhere:
# first argument is something called app, but not sure what but nil seems to be fine.
Strategies::MyStrategy.new(nil, *Devise.omniauth_configs[:mystrategy].args)

Display a Twitter feed from a Rails app

I have been able to have a user sign in with Twitter via OmniAuth (I followed Railscast #235-6 and made a simple application). Now I am trying to display the Twitter feed of the logged in user. Can anyone tell me how this is done? How do I initialize Twitter? How do I pass in the username and password of the logged in user? I am new to Rails so it would be helpful if I knew exactly where to put the code. Thanks
First, you don't need user credentials to get a Twitter feed if it's public. Look at the
Twitter gem. Once you install the gem, all you need to do is:
require 'twitter'
Twitter.user_timeline("icambron")
Try it out in IRB to get started. Pretty easy, right?
Now, you probably want to use your API key because Twitter limits anonymous requests, and it can be problematic from a shared server. Do that in an initializer:
Twitter.configure do |config|
config.consumer_key = YOUR_CONSUMER_KEY
config.consumer_secret = YOUR_CONSUMER_SECRET
config.oauth_token = YOUR_OAUTH_TOKEN
config.oauth_token_secret = YOUR_OAUTH_TOKEN_SECRET
end
Get the actual values from your Twitter developer page.
Finally, to get really fancy, if you want to scale up, you can make the request on behalf of the user, using the OAuth credentials that you got from OmniAuth (NOT their username and password; you don't have those). That will allow you to make a lot more requests per second, because they're coming from different users. Just initialize Twitter with the consumer_key and consumer_secret fields set to the stuff you got from the OmniAuth hash (see here, look under "credentials" to see how to get them from OmniAuth).
class Tweet
BASE_URL = "http://api.twitter.com/1.1/statuses/user_timeline.json"
SCREEN_NAME = "OMGFacts"
MAX_TWEETS = 10000
CONSUMER_KEY = "PMiAyrY5cASMnmbd1tg"
CONSUMER_SECRET = "0TYRYg0hrWBsr1YZrEJvS5txfA9O9aWhkEqcRaVtoA"
class << self
def base_url
BASE_URL
end
def screen_name
SCREEN_NAME
end
def url(count = MAX_TWEETS)
params = {:screen_name => screen_name, :count => count}
[base_url, params.to_param].join('?')
end
def prepare_access_token(oauth_token, oauth_token_secret)
consumer = OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET,
{ :site => "http://api.twitter.com",
:scheme => :header,
})
# now create the access token object from passed values
token_hash = { :oauth_token => oauth_token,
:oauth_token_secret => oauth_token_secret,
:open_timeout => 500000000
}
access_token = OAuth::AccessToken.from_hash(consumer, token_hash )
return access_token
end
def get(count = MAX_TWEETS)
count = Preference.get(:2000).to_i
access_token = prepare_access_token("178394859-cJlRaiQvqVusPAPjqC2Nn7r3Uc7wWsGua7sGHzs","3T8LCZTYXzuPLGzmWX1yRnKs1JFpfJLKemoo59Piyl8")
response = JSON.parse access_token.request(:get, url).body
response[0...count]
end
end
end

Problems with MailChimp API in Ruby Error Code: -90

I am using the following code in my MailChimp Controller to submit simple newsletter data. When It is sent I receive the following error as a "Method is not exported by this server -90" I have attached my controller code below. I am using this controller for a simple newsletter signup form. (Name, Email)
class MailchimpController < ApplicationController
require "net/http"
require "uri"
def subscribe
if request.post?
mailchimp = {}
mailchimp['apikey'] = 'f72328d1de9cc76092casdfsd425e467b6641-us2'
mailchimp['id'] = '8037342dd1874'
mailchimp['email_address'] = "email#gmail.com"
mailchimp['merge_vars[FNAME]'] = "FirstName"
mailchimp['output'] = 'json'
uri = URI.parse("http://us2.api.mailchimp.com/1.3/?method=listSubscribe")
response = Net::HTTP.post_form(uri, mailchimp)
mailchimp = ActiveSupport::JSON.decode(response.body)
if mailchimp['error']
render :text => mailchimp['error'] + "code:" + mailchimp['code'].to_s
elsif mailchimp == 'true'
render :text => 'ok'
else
render :text => 'error'
end
end
end
end
I highly recommend the Hominid gem: https://github.com/tatemae-consultancy/hominid
The problem is that Net::HTTP.post_form is not passing the "method" GET parameter. Not being a big ruby user, I'm not certain what the actual proper way to do that with Net::HTTP is, but this works:
require "net/http"
data="apikey=blahblahblah"
response = nil
Net::HTTP.start('us2.api.mailchimp.com', 80) {|http|
response = http.post('/1.3/?method=lists', data)
}
p response.body
That's the lists() method (for simplicity) and you'd have to build up (and urlencode your values!) your the full POST params rather than simply providing the hash.
Did you take a look at the many gems already available for ruby?
http://apidocs.mailchimp.com/downloads/#ruby
The bigger problem, and main reason I'm replying to this, is that your API Key is not obfuscated nearly well enough. Granted I'm used to working with them, but I was able to guess it very quickly. I would suggest immediately going and disabling that key in your account and then editing the post to actually have completely bogus data rather than anything close to the correct key. The list id on the other hand, doesn't matter at all.
You'll be able to use your hash if you convert it to json before passing it to Net::HTTP. The combined code would look something like:
mailchimp = {}
mailchimp['apikey'] = 'APIKEYAPIKEYAPIKEYAPIKEY'
mailchimp['id'] = '8037342dd1874'
mailchimp['email_address'] = "email#gmail.com"
mailchimp['merge_vars[FNAME]'] = "FirstName"
mailchimp['output'] = 'json'
response = nil
Net::HTTP.start('us2.api.mailchimp.com', 80) {|http|
response = http.post('/1.3/?method=listSubscribe', mailchimp.to_json)
}

Getting an Access Token with OAuth-Ruby and Tumblr API (Rails 3)

I am using OAuth-Ruby to do an OAuth authentication with a Tumblr application. I am able to write code that progresses through the various steps of OAuth, but I cannot get an access token or actually make a request. I can get a request key, redirect the user to Tumblr to authenticate and grant access, and receive an authenticated request key. But I can't get any farther than that.
I have registered my Tumblr application; let's assume for this question that it has provided me with the following keys:
OAuth Consumer Key: #oauth_consumer_key
Secret Key: #secret_key
(I have actual values, but I am keeping them concealed here for obvious reasons.)
I am running the following code within a controller that runs when the user submits a form, which form stores information in the #tumblog variable:
#0. provided when registering application
#key = #oauth_consumer_key
#secret = #secret_key
#site = 'http://www.tumblr.com'
#consumer = OAuth::Consumer.new(#key, #secret,
{ :site => #site,
:request_token_path => '/oauth/request_token',
:authorize_path => '/oauth/authorize',
:access_token_path => '/oauth/access_token',
:http_method => :post } )
if #consumer
#1. get a request token
#request_token = #consumer.get_request_token;
session[:request_token] = #request_token
session[:tumblog] = #tumblog
#2. have the user authorize
redirect_to #request_token.authorize_url
else
flash[:error] = "Failed to acquire request token from Tumblr."
render 'new'
end
This code gets me to the right page at Tumblr, where the user grants or denies my application access to the user's account. Assuming the user grants access, Tumblr redirects back to my application, to a callback I provided when I registered the application with Tumblr. To that point, everything works beautifully.
My OAuth callback runs the following code in the controller:
if params[:oauth_token] && params[:oauth_verifier]
#tumblog = session[:tumblog]
#request_token = session[:request_token]
#3. get an access token
#access_token = #request_token.get_access_token
. . . .
end
At Step 3, there is a problem. I cannot seem to actually get an access token with the line:
#access_token = #request_token.get_access_token
Can someone tell me what I need to do to get the access token? When I run that line, I get a OAuth::Unauthorized error.
I truly appreciate any advice. I've been Googling and trying different things for multiple days. Thanks!
i use Pelle's oauth plugin and modified it a little to support xauth like this :
require 'rubygems'
require 'oauth'
CONSUMER_KEY = 'YOUR_CONSUMER_KEY'
CONSUMER_SECRET = 'YOUR_CONSUMER_SECRET'
consumer = OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, :site => 'https://www.tumblr.com/oauth/access_token')
access_token = consumer.get_access_token(nil, {}, { :x_auth_mode => 'client_auth',
:x_auth_username => "some#email.com",
:x_auth_password => "password"})
tumblr_credentials = access_token.get('http://www.tumblr.com/api/authenticate')
puts access_token
puts access_token.token
puts access_token.secret
puts tumblr_credentials.body

Resources