Our Rails 3 app uses facebook-omniauth, allowing users to authenticate with facebook.
It'd be nice to use as much of the web based authentication system as possible, so I tried following the answer (the one that hasn't been down-voted) to this SO question but I can't get it to work.
The gist of the answer is:
omniauth-facebook will handle requests to the callback endpoint with an access_token parameter without any trouble. Easy :)
So to test this, in my browser I'm issuing the following request:
/users/auth/facebook_api/callback?access_token=BAAB...
But in my server log, I see:
(facebook) Callback phase initiated.
(facebook) Authentication failure! invalid_credentials: OAuth2::Error, :
{"error":{"message":"Missing authorization code","type":"OAuthException","code":1}}
I can't figure out what I'm doing wrong. Is the fact that I'm trying to do this through the browser to test messing something up? Any other ideas on how I can reuse my www based auth logic for my ios app?
UPDATE:
I'm not sure, but I'm following this guide in order to have multiple facebook omniauth strategies, one for www and another for mobile.
I never found a solution in line with what I was asking originally, but here is how I solved it: Take the access token you get on the iPhone and send it up to your server and perform the login manually.
def facebook_login
graph = Koala::Facebook::API.new(params[:user][:fb_access_token])
profile = graph.get_object('me')
omniauth = build_omniauth_hash(profile)
#user = User.find_or_create_for_facebook_oauth(omniauth)
end
On www we already had a method called find_or_create_for_facebook_oauth, and that took the result from Omniauth and either found the user or created a new one. In order to utilize that method for mobile, I had to build up a similar structure by hand so I could pass it as an argument.
def build_omniauth_hash(profile)
struct = OpenStruct.new
struct.uid = profile['id']
struct.info = OpenStruct.new
struct.info.email = profile['email']
struct.info.image = "http://graph.facebook.com/#{profile['id']}/picture?type=square"
struct.info.first_name = profile['first_name']
struct.info.last_name = profile['last_name']
struct.info.bio = profile['bio']
struct.info.hometown = profile['hometown']['name'] if profile['hometown']
struct.info.location = profile['location']['name'] if profile['location']
struct
end
Related
I'm playing around with the DocuSign's Ruby Quickstart app and I've done the following:
have an Admin account
have an organization
created an Integration(Connected App) for which I've granted signature impersonation scopes in the Admin Dashboard(made RSA keys, put callback urls, etc)
even if I've done the above, I've also made the request to the consent URL in a browser: SERVER/oauth/auth?response_type=code &scope=signature%20impersonation&client_id=CLIENT_ID &redirect_uri=REDIRECT_URI
Integration appears to have everything enabled
Then in the JwtCreator class the check_jwt_token returns true, updates account info correctly.
But when I try the following(or any other API call):
envelope_api = create_envelope_api(#args)
options = DocuSign_eSign::ListStatusChangesOptions.new
options.from_date = (Date.today - 30).strftime('%Y/%m/%d')
results = envelope_api.list_status_changes #args[:account_id], options
The api call raises an exception with DocuSign_eSign::ApiError (Unauthorized):
Args are:
#args = {
account_id: session[:ds_account_id],
base_path: session[:ds_base_path],
access_token: session[:ds_access_token]
}
All with correct info.
What am I missing?
For clarity, I was using some classes from the Quickstart app(like JwtCreator, ApiCreator, etc) along my code.
Not sure at this point if it's my mistake or part of the Quickstart app but this call:
results = envelope_api.list_status_changes #args[:account_id], options
the account_id was something like this "82xxxx-xxxx-xxxx-xxxx-xxxxxxxx95e" and I was always getting Unauthorized responses.
On a medium.com tutorial the author used the 1xxxxxx account_id and with this form, it worked.
I'm using Sorcery gem with External submodule. For some reason I'm not getting an email back from Facebook and I'm pretty sure I have things configured correctly. I'm trying to troubleshoot this further but I can't figure out how to read what data IS being returned via oauth to verify where things are breaking down. Where can I pry in and read this info? Thanks!
Here is my sorcery config.
Rails.application.config.sorcery.submodules = [:external]
Rails.application.config.sorcery.configure do |config|
config.external_providers = [:facebook, :google]
config.facebook.key = "#{Rails.application.secrets.sorcery_facebook_key}"
config.facebook.secret = "#{Rails.application.secrets.sorcery_facebook_secret}"
config.facebook.callback_url = "#{Rails.application.secrets.sorcery_facebook_callback_url}"
config.facebook.user_info_path = "me?fields=email,first_name,last_name"
config.facebook.user_info_mapping = {:email => "email"}
config.facebook.access_permissions = ["email"]
config.facebook.scope = "email"
config.facebook.display = "popup"
config.facebook.api_version = "v2.5"
config.user_config do |user|
user.authentications_class = Authentication
end
config.user_class = User
end
Well, technically this answers the question of how to find out what is being returned.
Inside your oauth controller if you call access_token.get('me?fields=email') or whatever fields you're wanting you'll get a response with a URL field set. Copy that URL into a browser and you'll get a JSON list of your data. In my case I get nothing with email but I'm able to return first_name, last_name, name. Not quite sure why I still can't get email, but hopefully this helps somebody troubleshoot in the future.
Another way would be to build the URL yourself if you have the access_token available.
https://graph.facebook.com/me?access_token=<access token goes here>&fields=first_name,last_name,email
Access token is retrievable with #access_token.token from oauth controller.
UPDATE
So silly...I had the config correct, but apparently had never logged out of Facebook since I'd made the proper corrections. Logging out and having oauth connect again seems to have fixed things.
I'm trying to integrate Withings with a rails apps. I'm using an Omniauth provider someone wrote called omniauth-withings. I was able to configure the provider to allow me to visit /auth/withings which redirects to the Withings authorization page. After I allow access, the browser is redirected to the callback url /auth/withings/callback. I have this routed to a controller action that attempts to get the measurement data from Withings using the simplificator-withings gem.
Withings.consumer_secret = ENV['withings_app_key']
Withings.consumer_key = ENV['withings_app_secret']
auth_hash = request.env['omniauth.auth']
user_id = auth_hash.extra.raw_info.body.users.first.id
withings_user = User.authenticate(user_id, auth_hash.credentials.token, auth_hash.credentials.secret)
measurements = withings_user.measurement_groups(:device => Withings::SCALE)
The problem happens when I call User.authenticate(), I get this:
An unknown error occurred - Status code: 2555
Is there something I'm missing here?
I was getting the same error with a django app. It turns out I was using the wrong token and secret. I was using the oauth_token and oauth_token_secret returned from step 1 of the authorization process, rather than the oauth_token and oauth_token_secret from step 3. Make sure you are using the values from step 3. The API documentation shows the same values returned from these calls, but they will be different. Hopefully this helps you too.
I'm setting the oauth_callback URL in the request header, when I do this in Twitter, it works fine and the user is redirected to the callback URL. But using Tumblr's API, the callback URL is ignored and the user is redirected to the default URL. Has anybody else experienced this? Is there anywhere else other than the header that I should be setting this? I tried passing it in as a parameter but that didn't really work either.
Any help would be appreciated.
According to Tumblr's developer blog, this was a bug in Tumblr's API and has been fixed.
Many of you have been dismayed that you could not override the
callback url when a user was attempting to authorize their
application. Good news: we’ve patched the bug that was causing this
particular issue.
Now, you can pass a url with the oauth_callback parameter and we will
redirect the user to that endpoint once you’re done.
Let’s go over a quick example.
When the user is presented with the screen to authorize your app, you
should be able to override your default callback with the
oauth_callback parameter in your url.
http://www.tumblr.com/oauth/authorize?oauth_token=your_token&oauth_callback=http%3A%2F%2Fmysite.com/oauth_callback/testing
The above url will redirect the user to
mysite.com/oauth_callback/testing and let you know if the user has
approved or denied your app.
Update March 14, 2013:
Starting today, Tumblr is no longer respecting the oauth_callback parameter. The blog post that I previously linked to has been deleted. I ended up using a variation of the accepted answer to work around it.
If you are trying a embed userid in callback url then this post can help you.
You can save your oauth token in a session and later on callback you can retrieve user from session.
on token request:
def ask_access
tumblr_consumer = get_consumer
if tumblr_consumer
#1. get a request token
request_token = tumblr_consumer.get_request_token
session[:request_token] = request_token
session[:user_token] = "#{request_token.params[:oauth_token]}_#{current_user.id}"
#2. have the user authorize
redirect_to request_token.authorize_url
else
render :text=> "Failed to acquire request token from Tumblr."
end
end
on call back:
def call_back
if params[:oauth_token] && params[:oauth_verifier]
request_token = session[:request_token]
user_id = session[:user_token].split("_")[1]
user = UserProfile.find user_id
##3. get an access token
access_token = request_token.get_access_token({:oauth_verifier => params[:oauth_verifier]})
user.tumblr_token = access_token.params[:oauth_token]
user.tumblr_secret = access_token.params[:oauth_token_secret]
user.save!
end
end
Tumblr does this (I assume) for security. They require that the callback URL is defined on application registration and they will not let it be overridden during implementation.
The security issue is to make sure that no one can steal your Application Token and try to use it to use your reputation to get access to customer's data. By forcing all callbacks to go to the default URL, they can guarantee that only your application is able get the Access Tokens.
The two ways to handle this are:
1) Have the default URL do a redirect to where you want it to go based on cookie or some other data
2) Have different application tokens for different callback URLs.
I can't respond to Jonathan Tran's answer, since my account is young, but posting the callback URL in the authorization URL no longer works, as he says. I asked on Twitter, and here was John Bunting's response:
https://twitter.com/codingjester/status/313248230987157505
I successfully was able to reroute my callback URL using the following (here in Python), after assigning all the proper keys:
consumer = oauth.Consumer(consumer_key, consumer_secret)
client = oauth.Client(consumer)
resp, content = client.request(request_token_url, "GET")
resp, content = client.request(request_token_url, "POST", body=urllib.urlencode({"oauth_callback": "[your own URL here]"}))
Tumblr implements this behavior differently from Twitter, so the same use of the Ruby OAuth library yields different results.
For your value of #callback_url, this works in Twitter:
#request_token = #oauth.get_request_token({
oauth_callback:#callback_url
})
redirect_to #request_token.authorize_url
But for Tumblr, you will be redirected to your default URL. To specify a different URL, you should do this:
#request_token = #oauth.get_request_token
redirect_to #request_token.authorize_url + '&' + { oauth_callback:#callback_url }.to_query
This is consistent with their documentation/blog post (cited in another answer). I have not checked to see if this is "correct" according to the OAuth 1.0a specification.
I'm trying to use Tornado's library for federated login to authenticate users and get access to their calendar, contacts, and mail. However, when I get the "mydomain.dyndns.info is asking for some information from your Google Account" message, the only bullet point listed is "Email Address". Subsequently, when I check the returned user object after I approve the request, the user object doesn't have an 'access_token' property.
Here's the code:
def get(self):
scope_list = ['https://mail.google.com/','http://www.google.com/m8/feeds/','http://www.google.com/calendar/feeds/']
...
self.authorize_redirect(scope_list, callback_uri=self._switch_command('auth_callback'), ax_attrs=["name","email"])
def _on_auth(self, user):
print 'in on auth'
if user:
self.set_the_user(user['email'])
session.set_data('usertoken_' + user['email'], user['access_token'])
self.redirect('/')
The uri that this spits out is:
https://www.google.com/accounts/o8/ud
?openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0
&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select
&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select
&openid.return_to=http%3A%2F%2Fmydomain.dyndns.info%3A333%2Fauth%2Fauth_callback%3Fperms%3Dgmail%26perms%3Dcontacts%26perms%3Dcalendar
&openid.realm=http%3A%2F%2Fmydomain.dyndns.info%3A333%2F
&openid.mode=checkid_setup
&openid.ns.oauth=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Foauth%2F1.0
&openid.oauth.consumer=mydomain.dyndns.info
&openid.oauth.scope=https%3A%2F%2Fmail.google.com%2F+http%3A%2F%2Fwww.google.com%2Fm8%2Ffeeds%2F+http%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds%2F
&openid.ns.ax=http%3A%2F%2Fopenid.net%2Fsrv%2Fax%2F1.0
&openid.ax.type.fullname=http%3A%2F%2Faxschema.org%2FnamePerson
&openid.ax.type.lastname=http%3A%2F%2Faxschema.org%2FnamePerson%2Flast
&openid.ax.type.firstname=http%3A%2F%2Faxschema.org%2FnamePerson%2Ffirst
&openid.ax.mode=fetch_request
&openid.ax.type.email=http%3A%2F%2Faxschema.org%2Fcontact%2Femail
&openid.ax.required=firstname%2Cfullname%2Clastname%2Cemail
Ideas: 1. maybe this has something to do with the fact I'm running on a local machine behind a dyndns forwarder? 2. Tornado's documentation says "No application registration is necessary to use Google for authentication or to access Google resources on behalf of a user" -- but maybe that's not true anymore?
If anyone has thoughts, I'd really appreciate it -- this is driving me a little batty!
Figured it out. You have to set the application properties google_consumer_key and google_consumer_secret.
application = tornado.web.Application(urlhandlers, cookie_secret=cookie_secret, google_consumer_key=google_consumer_key, google_consumer_secret=google_consumer_secret)
You get them by going here: https://www.google.com/accounts/ManageDomains