Using the 'oauth2' gem and a Heroku server, I have managed to create a client object and redirect the user to the login site:
client = OAuth2::Client.new(
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
:authorize_url => "/oauth/authorize",
:token_url => "/oauth/token",
:site => "https://connect.xxxxxxxxxx.com")
redirect_to(client.auth_code.authorize_url(:redirect_uri => 'https://xxxxx.herokuapp.com/callback'))
The browser afterwards redirects itself to the callback link as intended, something like:
https://xxxxx.herokuapp.com/callback?code=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I need to access the authorization code to then send a post request for the access token and refresh token, but being totally new to Ruby and Ruby on Rails, I am not sure how to get the callback and parse the code out. All of the dozen tutorials/documentation I've researched just mention that the authorization code should be 'magically obtained,' but I'm not sure how exactly that works explicitly. I tried creating a 'callback' controller and view to no avail - is there something missing in the routes files possibly? Help is much appreciated!
Your CallbackController will start to look like this maybe:
class CallbackController < ApplicationController
def index
access_token = client.auth_code.get_token(params[:code], redirect_uri: 'https://xxxxx.herokuapp.com/callback')
# Now you have an OAuth2::AccessToken object that you can either use to:
# - make direct requests to the API
# - or access access_token.token, access_token.refresh_token, access_token.expires_at, access_token.expires_in and store those
# somewhere for later use
# http://www.rubydoc.info/github/intridea/oauth2/OAuth2/AccessToken
end
private
def client
#client ||= OAuth2::Client.new(
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authorize_url: "/oauth/authorize",
token_url: "/oauth/token",
site: "https://connect.xxxxxxxxxx.com"
)
end
end
Related
I'm trying to implement OAuth as a sign-in option on my app. Most providers have been working really well, but Twitter has been giving me a ton of trouble.
I'm able to get a request token and be sent an oauth_verifier. However, after that I'm unable to get an authorized response from Twitter using the oauth-ruby gem.
I cannot use Devise or Omniauth in my setup.
Here is a minimalist and annotated version of my code:
# controllers/authorizations_controller.rb
# route: POST /auth/twitter
def create
if params[:oauth_token].blank? # This gets hit as the first part of the process. This entire block works as expected
oauth = Oauth::Twitter.get_token
session['oauth_token'] = oauth
render json: { oauth_token: oauth.token } # The user authorizes and is redirected (to this same action)
else # Once authorized, the oauth_token params is set and this gets executed
Oauth::Twitter.new(params, session['oauth_token'])
end
end
# models/oauth/twitter.rb
class Oauth::Twitter < Oauth::Base
def self.get_token # This seems to work
consumer.get_request_token(oauth_callback: 'http://localhost:3000') # This must be set to 'http://localhost:3000' per my frontend library
end
def initialize(params, request_token_hash)
consumer = self.class.consumer
request_token = OAuth::RequestToken.from_hash(consumer, request_token_hash) # This returns the expected RequestToken object
request_token.get_access_token(oauth_verifier: params[:oauth_verifier]) # This is where the 401 is thrown. params['oauth_verifier'] is confirmed to be set.
end
private
def self.consumer
OAuth::Consumer.new(
Rails.application.secrets['TWITTER_KEY'],
Rails.application.secrets['TWITTER_SECRET'],
:site => "https://api.twitter.com"
)
end
end
Unfortunately, I don't get any more information than a thrown `401 Authorization Required). Between the Twitter docs for sign-in via OAuth (more specifically, the oauth/access_token endpoint) and a similar example using the same OAuth gem, I can't see where my mistake lies.
I'm trying to write an integration with the LinkedIn API for my Rails app using OAuth2 but I'm having a hard time figuring out how to make authenticated API calls. I can't seem to find any resources on the topic that don't rely on the linkedin gem. I already have that gem installed in my app, but need to make some API calls that are not possible using that gem.
What I want to do is set up a controller with some endpoints that my Angular client can ping. When the client pings the controller, it should in turn ping the LinkedIn API. Once it gets a response it will perform some operations to store the information in the database and then pass it along to the Angular client.
I found a single code sample in Ruby from LinkedIn, which I'm attempting to modify into a controller for my app. Here is my controller:
class Employers::LinkedinApiController < ApplicationController
require 'net/http'
layout 'employers/layouts/application'
before_action :authorize_viewer
skip_after_filter :intercom_rails_auto_include
REDIRECT_URI = 'http://app.mysite.local:3000/linkedin/callback'
STATE = SecureRandom.hex(15)
CLIENT = parameterize(client.auth_code.get_token(STATE, redirect_uri: REDIRECT_URI))
def get_update(update_key=nil)
company = current_employer.company
update_key = company.social_media_posts.where("linkedin IS true AND linkedin_id IS NOT NULL").last.linkedin_id
page_id = company.linkedin.page_id
end_point = "https://api.linkedin.com/v1/companies/#{page_id}/updates/key=#{update_key}?format=json"
access_token = OAuth2::AccessToken.new(client, CLIENT['token'], {
:mode => :header,
:header_format => 'Bearer %s'
})
response = access_token.get(end_point)
# Do something with the response to save it to the database
# Pass response on to Angular client
respond_to do
format.json { render json: response }
end
end
private
#Instantiate your OAuth2 client object
def client
OAuth2::Client.new(
ENV['LINKEDIN_APP_ID'],
ENV['LINKEDIN_APP_SECRET'],
authorize_url: "/uas/oauth2/authorization?response_type=code",
token_url: "/uas/oauth2/accessToken",
site: "https://www.linkedin.com"
)
end
def parameterize(string)
parameters = {}
string.split('?')[-1].split('&').each do |param|
parameters[param.split('=')[0]] = param.split('=')[-1]
end
parameters
end
def authorize_viewer
if !employer_signed_in?
redirect_to new_employer_session_path
end
end
end
In the code sample from LinkedIn, the only way to get data from the accept method is to ping the authorize method, whose callback points to the accept method. What I can't figure out is how I can ping the get_update method in my controller and make an authenticated API call, without having to set up an authorize method that calls back to get_update.
How can I do this?
I'm trying to integrate my Rails app with Aweber via OAuth, using the official aweber gem.
If I follow their flow in the Rails console, I can get an access token, no problems:
oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
puts oauth.request_token.authorize_url
# => https://auth.aweber.com/1.0/oauth/authorize?oauth_token=xxxxxxxxxxxxxx
Then I visit that URL, type in my credentials, get a verification code, and go back to the rails console:
oauth.authorize_with_verifier 'xxxxxx'
# => #<OAuth::AccessToken>
Success!
The problem is, I want to do this in the real world, not just at the console, which means my Ruby code needs to be broken up into two separate actions. First, there's the controller action which redirects to Aweber's Oauth page:
def aweber
oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
redirect_to oauth.request_token(oauth_callback: "http://127.0.0.1:3000/auth/aweber/callback").authorize_url
end
Then there's the action which gets the access token after the user has input their credentials and been redirected:
def aweber_callback
oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
oauth.authorize_with_verifier(params[:oauth_verifier])
end
When I do it this way, the final line (authorize_with_verifier) always raises #<OAuth::Unauthorized: 401 Unauthorized>.
Seems like the problem is that I'm initializing the oauth variable twice, meaning I have two unrelated instances of AWeber::Oauth ... and only the instance of AWeber::Oauth that generated the authorize_url can get the access token. But I can't get the same instance in both aweber_callback and aweber because I'm dealing with two completely different threads and instances of the controller.
When I inspect oauth, I can see that the internal variables oauth.request_token.params["oauth_token"] and oauth.request_token.params["oauth_token_secret"] are different in each oauth, which I'm guessing is the cause of the problem. I can get the 'correct' oauth_token from the params (params[:oauth_token]), but I can't figure out how to get the correct oauth_token_secret (not to mention that manually setting instance variables like this feels very hacky and is probably not the best approach.)
How can I generate an access token?
I finally got this working by storing the oauth_token_secret in the session. (And I have to say, I'm very unimpressed by Aweber's documentation and API setup. This took 10 times longer than it should have.)
Gemfile
gem 'aweber', '~> 1.6.1', require: "aweber"
Routes
get "auth/aweber", to: "integrations#aweber", as: :aweber
get "auth/aweber/callback", to: "integrations#aweber_callback", as: :aweber_callback
Integrations Controller
def aweber
oauth = get_aweber_oauth
request_token = oauth.request_token(oauth_callback: aweber_redirect_uri)
session[:aweber_oauth_token_secret] = request_token.secret
redirect_to request_token.authorize_url
end
def aweber_callback
oauth = get_aweber_oauth
oauth.request_token = OAuth::RequestToken.from_hash(
oauth.consumer,
oauth_token: params[:oauth_token],
oauth_token_secret: session[:aweber_oauth_token_secret],
)
access_token = oauth.authorize_with_verifier(params[:oauth_verifier])
# TODO save access_token.token and access_token.secret
end
private
def get_aweber_oauth
AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
end
def aweber_redirect_uri
#_aweber_callback_uri ||= begin
if Rails.env.production?
redirect_host = "http://myproductionurl.com"
else
redirect_host = "http://127.0.0.1:3000"
end
"#{redirect_host}#{Rails.application.routes.url_helpers.aweber_callback_path}"
end
end
The next step is to store access_token.token and .secret in my DB,
then I'll be able to authorize users on future requests like this:
oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
oauth.authorize_with_access(current_user.aweber_token, current_user.aweber_secret)
aweber = AWeber::Base.new(oauth)
# Make calls using "aweber"...
I tried using the gem omniauth-aweber in combination with the omniauth gem, but I couldn't get it working (which is a shame, because I'm using other omniauth-xxx gems in this app and it would have been nice to keep things consistent.) Basically, that gem automatically handles the /auth/aweber part of the process, but after it redirects me back to /auth/aweber/callback/ I can't see any way to get the oauth_token_secret - it's not in the request params, the session, or the cookies.
I've answered my own question now but I'll give the bounty to anyone who can come up with an obvious improvement on the above, or figure out a way to make it all work with omniauth-aweber.
Reading through the AWeber API Ruby Library, this bit stands out
What if I don’t want to verify every time?
After verifying once, the oauth object contains an
oauth.access_token.token and and oauth.access_token.secret which may
be used to authorize your application without having to verify via
url:
... oauth.authorize_with_verifier('verification_code') puts 'Access
token: ' + oauth.access_token.token puts 'Access token secret: ' +
oauth.access_token.secret The token and secret can then be saved, and
authorization can be performed as follows:
require 'aweber'
oauth = AWeber::OAuth.new('consumer_key', 'consumer_secret')
#Rather than authorizing with the verification code, we use the token and secret
oauth.authorize_with_access(YOUR_ACCESS_TOKEN, YOUR_ACCESS_TOKEN_SECRET)
aweber = AWeber::Base.new(oauth)
So let's run through this:
You can create a class that keeps an object in memory for each User for enough time to finish the sign in and then save the token and secret for use until they expire.
Please note current_user is meant to be anything that uniquely identifies the user. You could use the session ID if your users aren't logged in yet at this point
class AWeberSignIn
def self.start_signing user
oauth = Rails.cache.fetch("#{user}/aweber", expires_in: 5.minutes) do
AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
end
oauth.request_token(oauth_callback: "http://127.0.0.1:3000/auth/aweber/callback").authorize_url
end
def self.authorize_with_verifier user, oauth_verifier
oauth = Rails.cache.fetch("#{user}/aweber")
oauth.authorize_with_verifier(oauth_verifier)
[oauth.access_token.token, oauth.access_token.secret]
end
def self.get_base_from_token token, secret
oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
oauth.authorize_with_access(token, secret)
AWeber::Base.new(oauth)
end
end
With this class, your controller methods become:
def aweber
redirect_to AWeberSignIn.start_signin current_user #Assuming you have a current_user helper. Use whatever gives you a unique value per user
end
def aweber_callback
token, secret = AWeberSignIn.authorize_with_verifier(current_user, params[:oauth_verifier])
#Do something with token and secret. Maybe save it to User attributes?
#You can then use them to get a AWeber base object via AWeberSignIn.get_base_from_token token, secret
end
Please note that this is using low-level Rails caching. Make sure you set up your caching technique if you want something different from the default
Since I saw that the omniauth-dropbox gem, was made to:
Authenticate to the Dropbox REST API (v1).
I was happy i wouldn't need to develop all the redirections the OAuth implies.
But I can't find a way to make them work together :(
The omniauth-dropbox gem, works fine, alone, I get authenticated and stuff.
But what to save from the callback, so the dropbox-sdk would understand the user is authenticated?
How to do so the session.get_access_token would be automatically handled by omniauth-dropbox?
CODE
def dropbox
session = DropboxSession.new(MULTIAPI_CONFIG['dropbox']['appKey'], MULTIAPI_CONFIG['dropbox']['appSecret'])
session.get_request_token
authorize_url = session.get_authorize_url('myurl/auth/dropbox/callback')
print authorize_url
session.get_access_token
client = DropboxClient.new(session, ACCESS_TYPE)
object = client.metadata('/')
render :json => object
end
ERROR
Couldn't get access token. Server returned 401: Unauthorized.
It looks to me from https://github.com/intridea/omniauth/wiki like you can get env['omniauth.auth'] in your callback handler, and from there you can extract the credentials (token and secret). See https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema.
Once you have the token and secret, you should be able to call session.set_access_token to tell the Dropbox SDK what credentials to use.
So I finally ended writing everything using the dropbox-sdk
Controller
class DropboxController < ApplicationController
def new
db_session = DropboxSession.new(MULTIAPI_CONFIG['dropbox']['appKey'], MULTIAPI_CONFIG['dropbox']['appSecret'])
begin
db_session.get_request_token
rescue DropboxError => e
render template: "multi_api/home/refresh"
end
session[:dp_request_db_session] = db_session.serialize
# OAuth Step 2: Send the user to the Dropbox website so they can authorize
# our app. After the user authorizes our app, Dropbox will redirect them
# to our 'dp_callback' endpoint.
auth_url = db_session.get_authorize_url url_for(:dp_callback)
redirect_to auth_url
end
def destroy
session.delete(:dp_authorized_db_session)
render :json => checkAuth
end
def isAuthenticated
render :json => checkAuth
end
def checkAuth
val = {'isAuthenticated' => false}
begin
unless not session[:dp_authorized_db_session]
dbsession = DropboxSession.deserialize(session[:dp_authorized_db_session])
client = DropboxClient.new(dbsession, MULTIAPI_CONFIG['dropbox']['accessType'])
val = {'isAuthenticated' => true}
end
rescue DropboxError => e
val = {'isAuthenticated' => false}
end
val
end
def callback
# Finish OAuth Step 2
ser = session[:dp_request_db_session]
unless ser
render template: "multi_api/home/refresh"
return
end
db_session = DropboxSession.deserialize(ser)
# OAuth Step 3: Get an access token from Dropbox.
begin
db_session.get_access_token
rescue DropboxError => e
render template: "multi_api/home/refresh"
return
end
session.delete(:dp_request_db_session)
session[:dp_authorized_db_session] = db_session.serialize
render template: "multi_api/home/refresh"
end
end
routes
get 'dp/logout', :to => 'dropbox#destroy'
get 'dp/login', :to => 'dropbox#new'
get 'dp/callback', :to => 'dropbox#callback', :as => :dp_callback
get 'dp/isAuthenticated', :to => 'dropbox#isAuthenticated'
multi_api/home/refresh.html.erb
<script type="text/javascript">
function refreshParent()
{
window.opener.location.reload();
if (window.opener.progressWindow)
window.opener.progressWindow.close();
window.close();
}
refreshParent();
</script>
Requesting dropbox
dbsession = DropboxSession.deserialize(session[:dp_authorized_db_session])
#client = DropboxClient.new(dbsession, MULTIAPI_CONFIG['dropbox']['accessType'])
I open a new tab when i want to authenticate a user to dropbox, when this is done I automatically refresh the original page and close the tab (see: multi_pi/home/refresh.html.erb).
Since I do all of this in javascript I need to know if the user has been authenticated successfully, that's why I provided a route dp/isAuthenticated which will return a json string containing a 'isAuthenticated' key set at true or false.
Connected users are not saved into a the database, only into the session. So when the session is destroyed they will have to re-authenticate. If you want them to be save into the database, then, you should dig into #smarx solution, using omniauth will be far easier.
I wrote my code here as an exemple for those who only want to depend on the ruby dropbox-sdk
omniauth-dropbox v0.2.0 uses the oauth1 API
dropbox-sdk v1.5.1 uses the oauth1 API
dropbox-sdk v1.6.1 uses the oauth2 API
With omniauth-dropbox v0.2.0 and dropbox-sdk v1.5.1, the following code in the controller action that omniauth redirects to works for me:
auth_hash = request.env['omniauth.auth']
access_token = auth_hash[:credentials][:token]
access_token_secret = auth_hash[:credentials][:secret]
session = DropboxSession.new(DROPBOX_APP_ID, DROPBOX_ADD_SECRET)
session.set_access_token(access_token, access_token_secret)
client = DropboxClient.new(session)
puts client.account_info.inspect
There might be a way to get omniauth-dropbox v0.2.0 and dropbox-sdk v1.6.1 working, but I haven't found it.
As James says, the most recent version of dropbox-sdk uses oauth2, so instead of omniauth-dropbox you need to use the oauth2 strategy:
https://github.com/bamorim/omniauth-dropbox-oauth2
Then the access_token will be present in the initial oauth response (as auth.credentials.token), and you can do with it as you will in the omniauth_callbacks_controller. I store it in an Authentication object.
For all those of you still looking for a tutorial for performing basically everything dropbox does, here's a tutorial I wrote a while ago:
http://blog.jobspire.net/dropbox-on-rails-4-0-ajax-using-dropbox-sdk/#more-54
You're welcome!
I have a rails project using https://github.com/intridea/oauth2. In my rails app I have the following code:
ApplicationController.rb
def facebook_client
OAuth2::Client.new(FacebookOauthCredentials::APP_ID, FacebookOauthCredentials::APP_SECRET, :site => 'https://graph.facebook.com')
end
FacebookController.rb
def facebook_session_create(poster, featured_item)
redirect_to facebook_client.web_server.authorize_url(:scope => 'publish_stream', :redirect_uri => "http://localhost:3000/facebook/facebook_callback")
end
def facebook_callback
if(params[:code])
begin
access_token = facebook_client.web_server.get_access_token(params[:code], :redirect_uri => "http://localhost:3000/facebook/facebook_callback")
access_token.post('/me/feed', "testing #{rand(1000)}")
rescue OAuth2::HTTPError => e
render :text => e.response.body
end
end
end
Every time I run this code I get this response:
{"error":{"type":"OAuthException","message":"Error validating verification code."}}
However, I use the sinatra app supplied in the OAuth2 gem's readme file, it works fine.
def client
OAuth2::Client.new(FacebookOauthCredentials::APP_ID, FacebookOauthCredentials::APP_SECRET, :site => 'https://graph.facebook.com')
end
get '/auth/facebook' do
redirect client.web_server.authorize_url(
:redirect_uri => redirect_uri,
:scope => 'publish_stream'
)
end
get '/auth/facebook/callback' do
access_token = client.web_server.get_access_token(params[:code], :redirect_uri => redirect_uri)
begin
user = JSON.parse(access_token.post('/me/feed', :message => "testing # {rand(10000)}"))
rescue Exception => e
return e.response.body
end
user.inspect
end
def redirect_uri
uri = URI.parse(request.url)
uri.path = '/auth/facebook/callback'
uri.query = nil
uri.to_s
end
I have tried reproducing the steps using irb, but I an http 400 error. I'm not sure if it's for the same reason as the rails app, or if it's because I'm doing a hybrid of console and web browser operation. Any suggestions would be greatly appreciated.
I found the answer to my problem on this page Facebook graph API - OAuth Token
I ran into the exact same problem but
it turned out the issue is not the
encoding of the redirect_uri
parameter, or that I had a trailing
slash or question mark it's simply
that I passed in two different
redirect urls (had not read the
specification at that time).
The redirect_uri is only used as a
redirect once (the first time) to
redirect back to the relying party
with the "code" token. The 2nd time,
the redirect_uri is passed back to the
auth server but this time it's not
used as you'd expect (to redirect)
rather it's used by the authentication
server to verify the code. The server
responds with the access_token.
You'll notice facebook documentation
(which is terrible) says fetch
"Exchange it for an access token by
fetching ....
"
In summary, I didn't have to encode or
do anything special to the Uri, just
pass in the same redirect_uri twice,
and fetch the 2nd page to get the
access_token inside.
I didn't copy my original code correctly and the redirect uri I was passing to get the code was different than the uri I was passing to get the access token. Facebook's API documentation is terrible :(
I had this problem/error but finally found a different solution (by comparing my Dev app settings which was working) to my prod app settings (which was generating this error).
I went to the advanced settings page for my app in the Facebook Developer app:
https://developers.facebook.com/apps/YourAppID/advanced
Then I found the "Encrypted Access Token:" setting and turn it to "Enabled."