facebookarchive / fbconsole

A micro api client for writing scripts against the Facebook Graph API.
Other
326 stars 74 forks source link

Automatic Facebook Authentication not working #25

Open nincehelser opened 11 years ago

nincehelser commented 11 years ago

I want to automatically authenticate into facebook without using a browser or any users involvement.

I've tried several pieces of sample code that says it will do that, but I always come back to this error. I think it might have something to do with a certificate?

Any insight would be appreciated.

Thanks!

Traceback (most recent call last): File "test.py", line 16, in 'http://local.fbconsole.com:8080/', File "/usr/local/lib/python2.7/dist-packages/fbconsole-0.3-py2.7.egg/fbconsole.py", line 427, in automatically_authenticate code = oauth["code"][0] KeyError: 'code'

pcardune commented 11 years ago

Can you post the code for your test script?

pdam commented 11 years ago

Hi guys , The error is the same that I see at my end .

        redirect_uri =  "https://127.0.0.1:8080"
        AUTH_SCOPE = ['publish_stream', 'publish_checkins', 'read_stream' , 'offline_access']
        fbconsole.SITE_URL=redirect_uri
        automatically_authenticate(self.fb_profileid , self.app_token  ,  fbconsole.ACCESS_TOKEN  , fbconsole.SITE_URL, True)
       automatically_authenticate(userName  , password  ,  fbconsole.ACCESS_TOKEN  , fbconsole.SITE_URL, True)
jeredbrent commented 11 years ago

Any luck getting this working? I'm also having this error.

jeredbrent commented 11 years ago

I'm pulling my hair out over here... When executing the "automatically_authenticate" function, 127.0.0.1:8080 never displays open in netstat -nat However, using the "authenticate" function I see that the basic http server running on 127.0.0.1:8080 until my desktop web browser is logged in and the success text displays. Shouldn't the automatic function still start the basic server to server the page? Do I need to call fbconsole.authenticate() before calling fbconsole.automatically_authenticate(xxx,xxx,xxx,xxx)?

I can manually fire up a basic webserver in another py script and can see response passed back to the web server.

Herp Derp...

nincehelser commented 11 years ago

I got what I needed to work. Here's what I ended up with.

#!/usr/bin/python
# coding: utf-8

# Most of this code I found on the Internet, but I don't recall where.  Don't ask me how it works. 
# It just does.  At least it just does as of today, 22 Aug 2013.
#
#  As-is, some weather data from my weather station and an image are posted on
#  Facebook under my user ID.
#  The machine sending the data can post the data without any manual involvement on my part.
#
# I hope this helps somebody. 
# 
# GDN
# george.nincehelser@gmail.com

import facebook
import urllib
import urlparse
import subprocess
import warnings

# Hide deprecation warnings. The facebook module isn't that up-to-date (facebook.GraphAPIError).
warnings.filterwarnings('ignore', category=DeprecationWarning)

# Parameters of your app and the id of the profile you want to mess with.
FACEBOOK_APP_ID     = 'your app id here'
FACEBOOK_APP_SECRET = 'your app secret here'
FACEBOOK_PROFILE_ID = 'your profile ID number here'

# Trying to get an access token. Very awkward. (original author's comment, not mine.  GDN)
oauth_args = dict(client_id     = FACEBOOK_APP_ID,
                  client_secret = FACEBOOK_APP_SECRET,
                  grant_type    = 'client_credentials')
oauth_curl_cmd = ['curl',
                  'https://graph.facebook.com/oauth/access_token?' + urllib.urlencode(oauth_args)]
oauth_response = subprocess.Popen(oauth_curl_cmd,
                                  stdout = subprocess.PIPE,
                                  stderr = subprocess.PIPE).communicate()[0]

try:
    oauth_access_token = urlparse.parse_qs(str(oauth_response))['access_token'] [0]  
except KeyError:
    print('Unable to grab an access token!')
    exit()

facebook_graph = facebook.GraphAPI(oauth_access_token)

# The following line reads a local file containing weather data to post on facebook.  GDN
linestring = open('/var/www/weewx/mobile.html', 'r').read()
print linestring

# The following lines attach an image and link to the posting.  GDN
attachment = {
        "name": "nincehelser.com",
        "link": "http://nincehelser.com",
        "caption": "Current Peru Weather",
        "description": linestring,
        "picture": "http://nincehelser.com/kneperu2/image.jpg"
        }

# Try to post something on the wall.
try:
    fb_response = facebook_graph.put_wall_post(linestring, attachment, profile_id  = FACEBOOK_PROFILE_ID )
    print fb_response
except facebook.GraphAPIError as e:
    print 'Something went wrong:', e.type, e.message

....

jeredbrent commented 11 years ago

Thanks @nincehelser ! This great stuff dude!

However, I need to use facebook_graph.put_photo() and this code errors because its just an app access token not a client token... (grant_type = 'client_credentials')

What I'm trying to accomplish is posting a photo that is taken with a raspberry pi to facebook whenever a physical button is pressed. Turns out that trying to do that with a headless device poses a bigger problem than I thought!

Your code does get me to the point where I can post a message and have a thumbnail and a link to to the image.

I looked at using IFTTT for copying the image to dropbox then having IFTTT post to facebook... but waiting 15 min for them to post is to slow for this purpose.

I suppose I could push the pic thats taken to dropbox public, bitly the link, then push that link to your code... kinda silly though as it will fill up the dropbox account with photos! hmm...

Hopefully the maintainer @pcardune is still alive!

nincehelser commented 11 years ago

I'm also working with a Pi. Let me know what you find out.

I don't understand why this authorization stuff has to be so cryptic, especially if you're trying to post to your own account.

kchr commented 9 years ago

I'm having the same problem here, but it seems to be related to using the custom redirect_uri http://local.fbconsole.com:8080 (as suggested in the README).

What this hostname really is resolving to is 127.0.0.1, which makes the Facebook auth API redirect the handshake to port 8080 on your local host. The redirect_uri is entered in your Facebook app settings.

However, for this redirect_uri to work something must be listening on port 8080 on your local host, to receive the information needed for creating the access token. When using authenticate(), fbconsole uses a built-in http server for this stage.

But for automatically_authenticate(), we have a different scenario with two pitfalls:

Using automatically_authenticate() with http://local.fbconsole.com:8080 as request_uri:

mechanize._response.httperror_seek_wrapper: HTTP Error 504: Connect to local.fbconsole.com:8080 failed: Connection refused

So what this means is basically, to be able to use auto authentication you must use another OAuth service (redirect_uri) to handle the final handshake. Or we would need to rewrite automatically_authenticate() so it starts the built-in http server in a background thread.

A third solution would be to rewrite the mechanizer part of this code to use the low-level API mechanize.open() instead of mechanize.Browser(). This way it is possible to subclass the request handlers, making it easier to hijack the login flow and just get the data needed before actually going to redirect_uri. This would make a built-in httpd redudant, but probably makes for more complex code (involving http handler classes).

Any other ideas? I'll fork in the meantime.

kchr commented 9 years ago

By running one app which uses the authenticate() method and then starting another app with a call to automatically_authenticate(), it is possible to get to the last stage in OAuth handshake (after redirect_uri).

The first app starts the builtin-in httpd and a browser window, and keeps the httpd running until the window is closed. Ignore the window (keep it open) and start the second app, which will try to connect to the built-in httpd set up right before. The first app will log something like this:

127.0.0.1 - - [15/Dec/2014 01:46:10] "GET /?code=[...]&state=[...] HTTP/1.1" 200 -G

However making some initial tests it seems that the OAuth protocol used in the auto auth method is currently broken. After a successful call to the redirect_uri (our local http server, the log line shown above), a final request is made for Facebook to complete the handshake - which seems to be impossible right now.

I keep getting these errors (last step, auto auth):

send: 'GET /oauth/access_token?code=[...]&client_secret=[...] ... reply: 'HTTP/1.1 400 Bad Request\r\n' header: WWW-Authenticate: OAuth "Facebook Platform" "invalid_code" "Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request"

The error message suggests the redirect_uri has been changed. Looking at the debug logs it is the same in all requests, as well as on the facebook app page. I'm comparing the contents of the code parameter sent in this request with the query data sent to our local httpd and it matches exactly.

Should it be permutated or obfuscated in any form?

pcardune commented 9 years ago

Thanks for looking into this @kchr. I'm not sure about the error code. In general the automated login implementation, even when made to work, seems fragile at best. If you are interested in using it and can get a pull request out that makes it work up to the point where you get that platform error, I can try digging into why that error occurs from the facebook side.

LA9SHA commented 9 years ago

Is it possible to get access to Facebook without any GUI and no manually input?

I want to add pictures by cron job from a Raspberry Pi.

pcardune commented 9 years ago

@LA9SHA, yes, but only if you already have an access token. It's not possible to get an access token without a GUI. So you would have to get an access token beforehand and bake it in to your app. In this scenario, it would be a good idea to get a long lived access token that lasts for 60 days. Once you have that, you can refresh the access token periodically to make sure it continues to work.

psineur commented 9 years ago

OK, seems like this is long-standing issue and I'm not sure if mechanize is actually a way we want to support this...

I have no idea how it works for now & would recommend using existing tokens, which you can obtain for example with Graph Explorer: https://developers.facebook.com/tools/explorer/?method=GET&path=me

On a related note, we may want to provide similar functionality with device login in the near future: https://developers.facebook.com/docs/facebook-login/using-login-with-devices But it's still in the making and looks like is not available to everyone yet.

kchr commented 9 years ago

I'm sorry if this seems like a rant; it's just some quick thoughts on the matter...

Personally, I think making it possible to generate new tokens using only the API code (forcing the user to give away its user login credentials) would allow an application for silent access to a larger scope than the end-users intention, or even send the credentials to some other server before/after going to Facebook. It would also enable malicious applications to change the end-users password, effectively locking the user out.

I think doing it through a browser session is a good way for the end-user to have control over what permissions it hands over to any app using the API, making malicious/bad code less potent and keeps the user login credentials away from the application/API implementations. The impact of potentially malicious code is heavily reduced.

@LA9SHA :

Is it possible to get access to Facebook without any GUI and no manually input? I want to add pictures by cron job from a Raspberry Pi.

As @pcardune already said: It's a one-time operation to get an access token.

Depending on your use case you might want to register your own app key and get a credential token for that app (which does not expire until you specifically say so), instead of a simple user access token (which expire after some time). You will have to register for a developer account and get an app key, then use that app key/token in your application. It's a few steps extra compared to generating a user token using someone elses app key, but in return it only has to be done once. This way is probably, in 9 out of 10 cases, what you are looking for if you are writing CLI apps/scripts which doesn't require user input or a GUI.

Registering as a developer and getting an app token is done here: https://developers.facebook.com/apps.

The browser verification step part where you allow the application to access your profile is inevitable because of the security reasons mentioned in the beginning of this reply, but it really only has to be done once.

lsoltero commented 9 years ago

Hello All,

So i have fbconsole with automatically_authenticate working... almost... and need your help.

bottom line... i can use fbconsole with the credentials for my fb account to post to /me/feed and /me/photos. yay! the problem is that I can't do it for anyone else.

Here is what I have done. A. create a facebook app. 1.record APP_ID and Secret

  1. set website URL to a real URL. It seems that autmatically_authenticate does not require a call back URL (as normal authenticate does) so just need to have the address of an actual web URL. this is required to keep FB happy. so i have http://www.globalmarinenet.com set for the site URL.
  2. advanced->client oauth login enabled
  3. app is public and available to all users

B. Here is a simple python script that i have been using to test authentication.

!/usr/local/bin/python

import urllib import fbconsole import sys,os,os.path sys.path.append("/usr/local/gmnlib/xgate/facebook")

fbconsole.OAUTH_SCOPE = ['publish_stream', 'read_stream'] fbconsole.APP_ID='XXXXXX' fbconsole.automatically_authenticate('EMAIL', 'PW', 'SECRET', 'http://www.globalmarinenet.com/', True)

profile_pic = fbconsole.graph_url('/zuck/picture') url=urllib.urlretrieve(profile_pic, 'zuck.jpg') print profile_pic

when executed with my credentials (the owner of the app) it works perfectly. I have another more complex script that allows me to upload pix/caption to the photos gallery without issue. However, If i try anyone else's credentials the post fails.

Anyone have any ideas why?

do i need to go through the process of a review to get this to work for everyone? I noticed that my App has the following approved items email, public_profile, user_friends. The permissions for posting seem to be missing. When I start the review process FB asks how to test the app and what the login procedure is. Not sure how to explain that we are using fbconsole or how to make the app available for them to test.

I have added testers and developers to the app and tried using their email and pw but that also fails.

Any thoughts on how to allow fbconsole to post for any user providing valid credentials would greatly be appreciated.

--luis

psineur commented 9 years ago

Hey! So long story short - automatic auth is a tricky thing, that doesn't work for me at all. It's not a part of API or anything we provide to developers officially... It probably will go away completely with reqeusts lib pull request merged & never come back.

The app review is a separate process, and has nothing to do with fbconsole nor graph explorer - it's a new system that was introduced to make sure real world (working for real users) applications are requesting only permissions, that are actually used & required for their app. To pass through review your app needs to be ready to use by normal non-technical users. If you use this app_id only for your internal testing while you're actually still working on your project - probably review team will suggest to use test users & developers on your app's dashboard to make permissions work.

As soon as your app is ready to be used by users - submit review & provide guidelines how everything is done and what is the flow of your app login with fb.

lsoltero commented 9 years ago

I find it curious that it doesn't work for you. I was having the issue listed above with the code = oauth["code"][0] KeyError: 'code' error.

As stated there was no listener on localhost:8080 which was causing the process to give the error.

What I found was that as long as you put the url to a valid website as the call back the problem goes away. For automatic_auth any valid url will work. The response from FB sends a redirect which mechanize must process. However... the response it not generated by the call back so the authentication code is processed correctly.

Please try it again using any valid web address and see if that works for you.

--luis

psineur commented 9 years ago

no luck. Using debug = True I was able to repro the requests with browser and successfully authenticated in fb & then app, but within fbconsole - it's awlays the same thing.

Looks like redirect must happen after POSTing a form, but instead server returns 200.

lsoltero commented 9 years ago

So why would it work for me and not you? could it be the version of mechanize?

mechanize: "0.2.5" python: 2.6.2 OS: FreeBSD

--luis

lsoltero commented 9 years ago

more on this issue... Although I continue to be able to use fbconsole to post to /me/feeds and /me/photos it only works for me. I created a test facebook user, made him a developer, and then in apps for my account invited the test user to be a developer of the application. The notice was sent to the test user, the test user accepted. However, when posting as that user authentication fails with the errors reported here.

So... currently stumped.

--luis

psineur commented 9 years ago

Can be a setting on user profile, test facebook users are different a bit, the main problem is that login never was built to be automated this way, so having e.g 2-factor auth can kill it...

Mac OS 10.10 / 10.9, python 2.7.6, Mechanize 0.2.5.

OK, so for the short term - I'm going to continue without auto authentication support, if anyone will have a patch that works for them - that's fine we can merge. If not - long term access tokens can be used instead ( https://developers.facebook.com/docs/facebook-login/access-tokens#extending )

For long term - when Device Login will be launched for Public ( https://developers.facebook.com/docs/facebook-login/using-login-with-devices ) - we will use it instead.

lsoltero commented 9 years ago

The Device Login procedure looks interesting. However, there are two concerns. A. It still requires user interaction. An 8 digi code is displayed to the user who then copies it down, goes to a browser on another computer, logins to FB, and then enters the code. Once this is done the device polls until it retrieves an access_token from FB which it then uses to post.

B. Although not explicitly stated the access_token returned by FB is probably either a standard short term (1hr) or long term (60day max) token which could expire if the user does not use the device to post to FB within the expiration period.

The nice thing about automatic_authentication is that once the user provides you with their FB authentication email/pw access to the users account for posting never expires. So the script could sit idle for 6 months and then post without issue unless the user changes his password.

What we need is a replacement for the post_offline token which was deprecated many years ago.

One thought that I have been meaning to investigate is using twitter to post to FB. Twitter has static tokens that never expire. Twitter also has an agreement with FB for cross posting. What I don't know is if the tokens provided to twitter by FB expire. I know that twitter and FB have a special relationship so my hope is that tokens provided by FB to twitter are of a different nature than the ones we have access to. The only way I know to test this is to setup a dummy FB and Twitter account, link them together, and then wait > 60 days before posting.

Any thoughts on this would greatly be appreciated.

--luis