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."
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.
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
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 controller action that may be hit by the client with a
oauth client token (no authenticated user), or may be hit by an authorized client with an access
token (for a specific user). I want to set up a nice little before filter to accomodate
this. I tried:
oauthenticate :strategies => [:oauth10_request_token, :oauth10_access_token],
:interactive => false,
:only => [:wonky_action]
If I try to hit this action with an access-token request, then it
complains because it tries to remember the OauthNonce twice. Here's
what my ClientApplication.verify_request method looks like:
def self.verify_request(request, options = {}, &block)
begin
signature = OAuth::Signature.build(request, options, &block)
nonce = OauthNonce.remember(signature.request.nonce, signature.request.timestamp)
unless nonce
Rails.logger.warn "Oauth request failing because of invalid nonce."
return false
end
value = signature.verify
unless value
Rails.logger.warn "Signature verification failed."
end
value
rescue OAuth::Signature::UnknownSignatureMethod => e
false
end
end
Looking at this, the behavior is no surprise. It runs through the
oauth10_request_token method, and remembers the nonce without a
problem, but the token is not a request token, so it fails. Then, it
tries to run through oauth10_access_token, and when it calls
verify_request the second time, it tries to remember a nonce that
we've already remembered.
The best solution I can come up with is to separate the Nonce-remembering from the rest of the request verification. In other words,
the server would verify the nonce first, and then do the signature
verification (or whatever else verify_request wants to do) as many
times as it pleases.
I don't see a clear way to do this without forking Pelle's
application_controller_methods file, but I'm hoping I'm just missing
the obvious fix that one of you will suggest! Thanks for your help!
In case you're interested, I'm using rails3 with pelle's oauth-plugin
and his fork of oauth:
gem 'oauth', :git => "http://github.com/pelle/oauth.git", :require => 'oauth/server'
gem "oauth-plugin", :git => 'https://github.com/pelle/oauth-plugin.git', :branch => 'rails3'