Ruby and Twitter: Getting Access Token from Request Token? - ruby-on-rails

I am currently in Step 3 of the processing on getting an oauth token/secret from an user trying to login via Twitter. https://dev.twitter.com/docs/auth/implementing-sign-twitter
Step 3 tells me to send this request to the API, but I am stuck as to how to do so. I currently have BOTH the oauth_token and oauth_verifier, but how do I send this POST request to get the oauth_token, oauth_token_secret pair?
Is there a standard Oauth Ruby gem I can use to send this POST request? I see examples online where I pass an #accessToken object, but i do not have such an object available. I just have the oauth_token and oauth_verifier (as strings). Given these 2 things, how do I convert them to an oauth_token and oauth_token_secret?
POST /oauth/access_token HTTP/1.1
User-Agent: themattharris' HTTP Client
Host: api.twitter.com
Accept: */*
Authorization: OAuth oauth_consumer_key="cChZNFj6T5R0TigYB9yd1w",
oauth_nonce="a9900fe68e2573b27a37f10fbad6a755",
oauth_signature="39cipBtIOHEEnybAR4sATQTpl2I%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1318467427",
oauth_token="NPcudxy0yU5T3tBzho7iCotZ3cnetKwcTIRlX0iwRl0",
oauth_version="1.0"
Content-Length: 57
Content-Type: application/x-www-form-urlencoded
oauth_verifier=uw7NjWHT6OJ1MpJOXsHfNxoAhPKpgI8BlYDhxEjIBY

Try something like the following rails controller actions, using the twitter and oauth gems:
def redirect
consumer = OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, {
:site => "https://api.twitter.com",
:scheme => :header
})
request_token = consumer.get_request_token(:oauth_callback => CALLBACK_URL)
session[:twitter_request_token] = request_token
redirect_to request_token.authorize_url #=> "https://api.twitter.com/oauth/authorize?oauth_token=XYZ"
end
def callback
request_token = session[:twitter_request_token]
access_token = request_token.get_access_token(:oauth_verifier => params[:oauth_verifier])
client = Twitter::REST::Client.new(
:consumer_key => CONSUMER_KEY,
:consumer_secret => CONSUMER_SECRET,
:access_token => access_token.token,
:access_token_secret => access_token.secret
)
twitter_user = client.user
redirect_to root_url # or do something with the twitter_user
end
See also: http://barkingiguana.com/2009/10/13/twitter-oauth-authentication-using-ruby/

yes there is the Omniauth gem for authentication with Twitter. The documentation is straight forward.
I personally use Omniauth integrated with Devise and the Twitter gem to access Twitter - works very well.
Hope this helps,
Eugen

The common procedure is the following:
You shell to register your app on twitter development page.
Then set the proper Name, Description, and Website values up for your application.
App Name
App Description
http://your_app_domain.zone:3000/
Change Application Type is your app, by default it has read only access type.
Setup the callback URL for yuor application:
http://your_app_domain.zone:3000/auth/twitter/callback
Store the all keys, and secrets that are shewn on the OAuth tool twitter page:
Consumer key:
Consumer secret:
Access token:
Access token secret:
Setup route on your site with devise, or devise-like gem with the specified twitter keys, and secrets to enable authentication engine. The route list now shall include /auth/twitter path.
By going to http://your_app_domain.zone:3000/auth/twitter you will be redirected to twitter site, and dropped back to your site with passed oauth_token
But
You simple receive those keys, and secrets, and apply then in your app, avoiding the 6, and 7 points:
client = Twitter::REST::Client.new do |config|
config.consumer_key = "YOUR_CONSUMER_KEY"
config.consumer_secret = "YOUR_CONSUMER_SECRET"
config.access_token = "YOUR_ACCESS_TOKEN"
config.access_token_secret = "YOUR_ACCESS_SECRET"
end

Related

Getting authorization from iNaturalist for API

I'm trying to use iNaturalist's API via Ruby on Rails. I'm new to Ruby and iNaturalist's documentation is pretty sparse. As a first step, I need to figure out how to get authorization from their site.
iNaturalist provides the sample code below. I set up a project with iNaturalist and tried running the sample code in Rails Console with my credentials. #{url} in the following line is replaced with a url that the user is supposed to go to in order to log in to iNat:
puts "Go to #{url}, approve the app, and you should be redirected to your " +
"redirect_uri. Copy and paste the 'code' param here."
I went to the resulting url and logged in:
https://www.inaturalist.org/oauth/authorize?client_id=[my client id]&redirect_uri=https://ruby_on_rails--timrobinson41199691.codeanyapp.com/login/&response_type=code
iNaturalist responds with "The redirect uri included is not valid."
If I leave off &response_type=code, it responds with "The authorization server does not support this response type."
My website is on codeanywhere.com. The url of the main page is "https://ruby_on_rails--timrobinson41199691.codeanyapp.com/". Part of the problem is that I don't understand what kind of page I'm supposed to create for redirect_uri, since I'm still kind of new at this.
require 'rubygems'
require 'rest_client'
require 'json'
site = "https://www.inaturalist.org"
app_id = 'YOUR APP ID'
app_secret = 'YOUR APP SECRET'
redirect_uri = 'YOUR APP REDIRECT URI' # you can set this to some URL you control for testing
# REQUEST AN AUTHORIZATION CODE
# Your web app should redirect the user to this url. They should see a screen
# offering them the choice to authorize your app. If they aggree, they will be
# redirected to your redirect_uri with a "code" parameter
url = "#{site}/oauth/authorize?client_id=#{app_id}&redirect_uri=#{redirect_uri}&response_type=code"
# REQUEST AN AUTH TOKEN
# Once your app has that code parameter, you can exchange it for an access token:
puts "Go to #{url}, approve the app, and you should be redirected to your " +
"redirect_uri. Copy and paste the 'code' param here."
print "Code: "
auth_code = gets.strip
puts
payload = {
:client_id => app_id,
:client_secret => app_secret,
:code => auth_code,
:redirect_uri => redirect_uri,
:grant_type => "authorization_code"
}
puts "POST #{site}/oauth/token, payload: #{payload.inspect}"
puts response = RestClient.post("#{site}/oauth/token", payload)
puts
# response will be a chunk of JSON looking like
# {
# "access_token":"xxx",
# "token_type":"bearer",
# "expires_in":null,
# "refresh_token":null,
# "scope":"write"
# }
# Store the token (access_token) in your web app. You can now use it to make authorized
# requests on behalf of the user, like retrieving profile data:
token = JSON.parse(response)["access_token"]
headers = {"Authorization" => "Bearer #{token}"}
puts "GET /users/edit.json, headers: #{headers.inspect}"
puts RestClient.get("#{site}/users/edit.json", headers)
puts
After the user logs in to iNat, he should be redirected back to my website with the authorization code provided in the data. In routes.rb, my login route is set as:
post '/login', to: 'organisms#login'
I've tried using get, as well.
iNat is returned the error mentioned above and not redirecting back to my site.
OAuth can be a bit daunting at first. And that guide really just shows the equivalent of using cURL to test your API.
In an actual application redirect_uri is whatever endpoint in your application that handles the response when the provider redirects back from authorization.
So lets setup a minimal real rails app.
1. Register your app
Register a new application or edit your existing app.
Use http://localhost:3000/oauth/inaturalist/callback for the callback url (adjust the host as needed).
Keep the window open as you will need the client_id and secret in a moment.
2. Setup your routes
# /config/routes.rb
Rails.application.routes.draw do
# just make sure you have a root path defined.
root to: 'pages#home'
namespace :oauth do
namespace :inaturalist, controller: :callbacks do
# This is just a simple redirect route
get '/', action: :passthru, as: :authorize
# This is the route that handles the actual callback
get 'callback'
end
end
end
You can actually do this without the redirect route and just plant a link to the https://www.inaturalist.org/oauth/authorize... url in your view. But having it isolates your application against the craziness that is OAuth and its how OmniAuth does it.
3. Add your credentials to the Rails app.
In Rails 5 use the encrypted credentials to store your client_id and secret.
Run $ bin/rails credentials:edit from your shell.
inaturalist:
client_id: <from the inaturalist site>
secret: <from the inaturalist site>
In earlier versions use ENV vars instead.
4. Install the oauth2 gem
# Place this in your gemfile outside any groups
gem 'oauth2', '~> 1.4', '>= 1.4.1'
Then run bundle install.
4. Setup the controller
# app/controllers/oauth/inaturalist/callbacks_controller.rb
require 'oauth2'
module Oauth
module Inaturalist
class CallbacksController < ::ActionController::Base
# GET /oauth/inaturalist
def passthru
redirect_to client.auth_code.authorize_url
end
# This endpoint is where the provider redirects the user back to
# after authorization.
# GET /oauth/inaturalist/callback
def callback
# Fetch an auth token from the access code
token = client.auth_code.get_token(params[:code])
# Perform an authenticated request to get the users data
api_response = token.get("/users/edit.json")
#user_data = JSON.parse(api_response.body)
# This is just an example of how you can use the user data from
# the provider
#user = {
uid: #user_data["id"],
nickname: #user_data["nickname"]
}
session[:user_id] = #user[:uid]
session[:token] = token.to_hash
redirect_to root_path, success: "Hello #{#user[:nickname]}"
end
private
# Change this if you are not using Rails 5 credentials.
def client
OAuth2::Client.new(
credentials.fetch(:client_id),
credentials.fetch(:secret),
site: "https://www.inaturalist.org",
redirect_uri: oauth_inaturalist_callback_url
)
end
def credentials
Rails.application.credentials.fetch(:inaturalist)
end
end
end
end
token here is actually a new OAuth2::AccessToken instance that can be called to call endpoints with the fetched credentials.
This example stores the token in the session. You can retrieve it in subsequent requests with:
token = OAuth2::AccessToken.from_hash( session[:token] )
The docs kind of mention trading the oauth access token for an api token for api.inaturalist.org. But the details are kind of sparse.
5 Add a link to sign in:
<%= link_to 'Sign in to iNaturalist.org', oauth_inaturalist_authorize_path %>

Rails Google Client API - unable to exchange a refresh token for access token

After struggling with some SSL issues on my machine, I'm still trying to access a user's Blogger account through the Google Ruby Client API. I'm using the following:
Rails 3.2.3
Ruby 1.9.3
oauth2 (0.8.0)
omniauth (1.1.1)
omniauth-google-oauth2 (0.1.13)
google-api-client (0.4.6)
I can successfully authenticate users and access their blogs through the Google API at the time of authentication. When a user logs in, I store the access_token and refresh_token I receive from Google. and everything works great until the access_token expires. I'm trying to build the functionality that exchanges the refresh_token for a new access_token, but keep coming up against walls. Using the client documentation as an example, this is the code I'm using:
client = Google::APIClient.new
token_pair = auth.oauth_token # access_token and refresh_token received during authentication
# Load the access token if it's available
if token_pair
client.authorization.update_token!(token_pair.to_hash)
end
# Update access token if expired
if client.authorization.refresh_token && client.authorization.expired?
client.authorization.fetch_access_token!
end
blogger = client.discovered_api('blogger', 'v3')
result = client.execute(
api_method: blogger.blogs.list_by_user,
parameters: {'userId' => "self", 'fields' => 'items(description,id,name,url)'},
headers: {'Content-Type' => 'application/json'})
This code works perfectly while the access_token is valid. As soon as it expires though, I'm seeing 2 problems:
Even though I know the token is expired (I've checked expires_at value in the database), client.authorization.expired? returns false -- is there a different way I can check the expiration of the token besides using the value in the database?
When I force the execution of client.authorization.fetch_access_token! I get an invalid_request error.
Can someone please let me know how I can exchange a refresh_token for a new access_token using the client API? Even if you know how to do it in another language, that would be a big help as I can then try to Rubyfy it. Thanks!!
You may have already found this, but you can read through the whole process here at google: https://developers.google.com/accounts/docs/OAuth2WebServer
The omniauth-google-oauth2 strategy already takes care of setting the access_type and approval_prompt so getting a refresh token is just a matter of posting to https://accounts.google.com/o/oauth2/token with grant_type=request_token
Here is roughly the code I use:
def refresh_token
data = {
:client_id => GOOGLE_KEY,
:client_secret => GOOGLE_SECRET,
:refresh_token => REFRESH_TOKEN,
:grant_type => "refresh_token"
}
#response = ActiveSupport::JSON.decode(RestClient.post "https://accounts.google.com/o/oauth2/token", data)
if #response["access_token"].present?
# Save your token
else
# No Token
end
rescue RestClient::BadRequest => e
# Bad request
rescue
# Something else bad happened
end
Since you are using the Ruby Google API Client, why not use it to exchange the refresh token as well? The Ruby API does pretty much the same thing internally, which #brimil01 has said in his answer.
This is how I use the Ruby API to exchange my refresh token for a new access token.
def self.exchange_refresh_token( refresh_token )
client = Google::APIClient.new
client.authorization.client_id = CLIENT_ID
client.authorization.client_secret = CLIENT_SECRET
client.authorization.grant_type = 'refresh_token'
client.authorization.refresh_token = refresh_token
client.authorization.fetch_access_token!
client.authorization
end
And according to this issue here, it is recommended not to use the expired? method to check if an access token has expired.
Basically, don't call the expired? method. There are essentially zero
scenarios where that's a good idea. It simply won't give you reliable
expiration information. It's more of a hint than a real expiration
timestamp, and the token server may decide to honor an expired token
anyways in certain somewhat theoretical, but important, circumstances.
If you do get an invalid grant error, always refresh your access token
and retry once. If you're still getting an error, raise the error.
Here is what I do.
# Retrieved stored credentials for the provided user email address.
#
# #param [String] email_address
# User's email address.
# #return [Signet::OAuth2::Client]
# Stored OAuth 2.0 credentials if found, nil otherwise.
def self.get_stored_credentials(email_address)
hash = Thread.current['google_access_token']
return nil if hash.blank?
hash[email_address]
end
##
# Store OAuth 2.0 credentials in the application's database.
#
# #param [String] user_id
# User's ID.
# #param [Signet::OAuth2::Client] credentials
# OAuth 2.0 credentials to store.
def self.store_credentials(email_address, credentials)
Thread.current['google_access_token'] ||= {}
Thread.current['google_access_token'][email_address] = credentials
end
def self.credentials_expired?( credentials )
client = Google::APIClient.new
client.authorization = credentials
oauth2 = client.discovered_api('oauth2', 'v2')
result = client.execute!(:api_method => oauth2.userinfo.get)
(result.status != 200)
end
# #return [Signet::OAuth2::Client]
# OAuth 2.0 credentials containing an access and refresh token.
def self.get_credentials
email_address = ''
# Check if a valid access_token is already available.
credentials = get_stored_credentials( email_address )
# If not available, exchange the refresh_token to obtain a new access_token.
if credentials.blank?
credentials = exchange_refresh_token(REFRESH_TOKEN)
store_credentials(email_address, credentials)
else
are_credentials_expired = credentials_expired?(credentials)
if are_credentials_expired
credentials = exchange_refresh_token(REFRESH_TOKEN)
store_credentials(email_address, credentials)
end
end
credentials
end
I fixed it with simple code below.
def refesh_auth_tooken(refresh_token)
client = Google::APIClient.new
puts "REFESH TOOKEN"
client.authorization = client_secrets
client.authorization.refresh_token = refresh_token
#puts YAML::dump(client.authorization)
client.authorization.fetch_access_token!
return client.authorization
end

Unable to get access token with oauth_verifier, keep getting 401 error

I saw many questions about the same problem, but I just cannot find a way around so I'm asking a question to be sure I did not miss a thing.
On twitter, I set the callback_url to 'https://my_app_url/'.
# consumer
#consumer = OAuth::Consumer.new('consumer_id',
'secret_key',
:site => 'https://api.twitter.com')
# request_token
#request_token = #consumer.get_request_token(:oauth_callback => "https://my_app_url/?state=#{state}")
# then get the user to log in via
login_url = #request_token.authorize_url
# I have a params[:oauth_token] and params[:oauth_verifier] in return
# I don't know what the oauth token is for
# I then should be able to get an access token
# all oauth calls are done in an object so #request_token has been conserved.
#access_token = #request_token.get_access_token(:oauth_verifier => params[:oauth_verifier])
# then it happens.
# => 401 Unauthorized
I have no idea why the 401 is being raised. Is there a step I have been missing?
I would appreciate any help.
get_request_token gives you temporary OAuth tokens to authenticate you during the OAuth authentication flow. The OAuth verifier is something that proves that the user authorized the application. You have to give this last to get_access_token in order to get the final OAuth tokens that will be used for all your authenticated requests.
For further informations, consider reading the official Twitter documentation dealing with this : https://dev.twitter.com/docs/auth/using-oauth (https://dev.twitter.com/docs/auth/implementing-sign-twitter for the description of the process)

How to the same thing with omniauth

Reading on this oauth tutorial.
I would like to do exactly the same thing using the Omniauth gem, to get the access tokens from Twitter's API.
#oauth = OAuth::Consumer.new(APP_CONFIG['twitter_consumer_key'], APP_CONFIG['twitter_consumer_secret'], :site => 'http://api.twitter.com', :request_endpoint => 'http://api.twitter.com', :sign_in => true)
# Get the request tokens from the API
rt = oauth.get_request_token
rtoken = rt.token # request token
rsecret = rt.secret # request token secret
# Go to url and click ""Allow access” when prompted
# http://api.twitter.com/oauth/authorize?oauth_token=your_request_token_from_above
# Get the access tokens from the API
at = rt.get_access_token
oauth_token = at.token
oauth_token_secret = at.secret
Anyone know how this can be achieved?
Here it is a good howto, take a look:
http://philsturgeon.co.uk/blog/2010/11/using-omniauth-to-make-twitteroauth-api-requests

How to achieve the same thing for the Facebook API?

Reading on this oauth tutorial.
The code below, shows how this can be achieved to get the access tokens on Twitter's API. I would like to do exactly the same thing for the Facebook API using OAuth.
#oauth = OAuth::Consumer.new(APP_CONFIG['twitter_consumer_key'], APP_CONFIG['twitter_consumer_secret'], :site => 'http://api.twitter.com', :request_endpoint => 'http://api.twitter.com', :sign_in => true)
# Get the request tokens from the API
rt = oauth.get_request_token
rtoken = rt.token # request token
rsecret = rt.secret # request token secret
# Go to url and click ""Allow access” when prompted
# http://api.twitter.com/oauth/authorize?oauth_token=your_request_token_from_above
# Get the access tokens from the API
at = rt.get_access_token
oauth_token = at.token
oauth_token_secret = at.secret
Anyone know how this can be achieved?
Look at "OAuth URLs and tokens" on the Koala docs: https://github.com/arsduo/koala/wiki/OAuth It is a great gem for FB.

Resources