Noi Sek
Noi Sek

Reputation: 544

"Invalid Response from Facebook" when Authenticating with Facebook using Flask-Oauthlib

I'm consistently getting an "Invalid response from Facebook" error when authenticating over Facebook with Oauthlib when building off of the sample code here.

I've outlined the sections of relevant code below.

Setup:

Setting up the Oauth request object.

Not pictured: Navigational routes and Flask app initialization.

from flask_oauthlib.client import OAuth, OAuthException

oauth = OAuth()

facebook = oauth.remote_app('facebook',
  base_url='https://graph.facebook.com/',
  request_token_url=None,
  access_token_url='/oauth/access_token',
  authorize_url='https://www.facebook.com/dialog/oauth',
  consumer_key=config.get("app_id"),
  consumer_secret=config.get("app_secret"),
  request_token_params={'scope': 'public_profile,email'}
)

@facebook.tokengetter
def get_facebook_token():
  if current_user.is_authenticated():
    return current_user.get_facebook_token()

  else:
    return None

Login handler:

Sending users here in order to begin the process, with the url for the facebook callback appended to the root URL.

@app.route('/facebook/login')
def facebook_login():
  return facebook.authorize(callback="http://example.com%s" % url_for('facebook_callback'))

Facebook callback, source of the issue:

From here I can garner that a code (presumably the token) is returned but Oauthlib fails to parse it correctly.

@app.route('/facebook/callback')
def facebook_callback(response):
  response = facebook.authorized_response()
  if response is None:
    flash("You denied the request to sign in.", "error")
    return redirect(url_for('index'))

  if isinstance(response, OAuthException):    
    flash("Access denied: %s" % response.message, "error")
    return redirect(url_for('index'))

  # Request fails here, returns the redirect above.

From dumping the request args I can see fairly clearly that after being directed to Facebook and successfully connecting, there is a very long token being returned to the callback along the lines of '?code=1234567890-abcdefghijklmnop', however actually trying to authenticate with this fails with "Invalid response from Facebook".

Here is a sample request dump:

ImmutableMultiDict([('code', 'AQAPedwWavTb_cBx6NVy-qy3AL5YPr780oG5tA1LfITpVwkk-kr_4I0lG6B-vlhcYEubhuKhEM46bPs-1WpWUpJzcWfhkQ64lIkdLm9uSHSqeBHBM_6zw7SDpvVmGK-JKWBpAqRJuBKenl9zslQizthox96104iiul0uYQY67cmZgPXZi9uL-mcgZ5dRj387eKJIjNninBXxwCGgFkg5kLVHYt7t0ktUH58stYlxn2f98AXuSlrIvWsA5NeHsVbM8XY0XQrDrNbCvjDmEwHQGkZ3uZRbyaecN7MAi0bM0TrZzpuQ8j3M34DnQp_v9n4ktM4')])

Having used similar code based off of the Twitter sample that works, I'm thinking this could be a possible library bug due to Facebook API changes, but I would appreciate any pointers!

Upvotes: 2

Views: 1811

Answers (1)

Noi Sek
Noi Sek

Reputation: 544

For anyone who stumbles upon this from Google in the future, I solved this in a solution that can be read here.

Hey there, I solved this issue in a very hacky way which I would not recommend for production environments, but I eventually found the issue a few days after my last message.

When you ask Facebook for an access token, it does NOT give you an access token in the way you might expect. What I assumed to be a failure on Facebook's side was instead a (perhaps intentional) formatting error.

What you might expect:

http://example.com/callback?access_token=00000000000

or

http://example.com/callback with the access token passed as a POST argument in the headers.

What actually happens is that Facebook responds like this:

http://example.com/callback?#access_token=0000000000

Because of this, it is -impossible- for any server side language to parse it, as the access token will now only be visible to the browser itself. It is not passed to the backend whatsoever.

Capturing the request:

@app.route('/facebook/translate', methods=['GET'])
def facebook_translate():
  # Facebook responds with the access token as ?#access_token, 
  # rather than ?access_token, which is only accessible to the browser.
  # This part is where things get really, really dumb.
  return '''  <script type="text/javascript">
    var token = window.location.href.split("access_token=")[1]; 
    window.location = "/facebook/callback?access_token=" + token;
  </script> '''

Proceeding as usual:

@app.route('/facebook/callback', methods=['GET', 'POST'])
def facebook_callback():
  access_token = request.args.get("access_token")

  if access_token == "undefined":
    flash("You denied the request to sign in.", "error")
    return redirect(url_for('index'))

  graph = facebooksdk.GraphAPI(access_token)
  profile = graph.get_object("me")

Upvotes: 1

Related Questions