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!
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")
Related
I hope everyone is fine. I am trying to implement google sso on my fastapi app. after entering the user credentials is entered and it gets redirected while redirecting i am getting this error
google_sso = GoogleSSO("client-id", "client-secret", "http://127.0.0.1:8000/google/callback/")
#app1.get("/google/login")
async def google_login():
"""Generate login url and redirect"""
return await google_sso.get_login_redirect()
#app1.get("/google/callback")
async def google_callback(request: Request):
"""Process login response from Google and return user info"""
user = await google_sso.verify_and_process(request)
print("Hellooooooooooooooo")
print(user, "11111111111111")
return {
"id": user.id,
"picture": user.picture,
"display_name": user.display_name,
"email": user.email,
"provider": user.provider,
}
I have shared the URL configuration in google dashboard in below screenshot
enter image description here
the error i have mentioned below
oauthlib.oauth2.rfc6749.errors.CustomOAuth2Error: (redirect_uri_mismatch) Bad Request
The problem could lay in the process_login() function which is getting called in the verify_and_process() function in your /callback api.
Let's take a look inside the process_login() function (https://tomasvotava.github.io/fastapi-sso/sso/base.html#fastapi_sso.sso.base.SSOBase.verify_and_process):
async def process_login(self, code: str, request: Request) -> Optional[OpenID]:
"""This method should be called from callback endpoint to verify the user and request user info endpoint.
This is low level, you should use {verify_and_process} instead.
"""
url = request.url
current_url = str(url).replace("http://", "https://")
current_path = f"https://{url.netloc}{url.path}"
I guess the (redirect_uri_mismatch) error is because you are using a HTTP redirect_url in your GoogleSSO() call:
google_sso = GoogleSSO("client-id", "client-secret", "http://127.0.0.1:8000/google/callback/")
Inside the process_login() function the HTTP of the redirect url inside the url of your request is replaced with HTTPS:
url = request.url
current_url = str(url).replace("http://", "https://")
After that replacement you have a mismatch with your redirect url, because
https://127.0.0.1:8000/google/callback/
is not
http://127.0.0.1:8000/google/callback/
They are two different urls.
Solution could be that you secure your server with HTTPS via self signed certificates.
(That one is very simple: https://dev.to/rajshirolkar/fastapi-over-https-for-development-on-windows-2p7d)
Btw. did you register you app in the google cloud (https://developers.google.com/identity/sign-in/web/sign-in)? Because you are using "client-id" and "client-secret" as parameters.
try it use 127.0.0.1:8000/google/callback #remove /
or
fix url #app1.get("/google/callback/") #add /
This is because the port number is changing in the redirect URI, everytime you run the application.
So everytime you run it it becomes:
http://localhost:65280/
http://localhost:65230/
http://localhost:63280/
And so forth. I dont have a solution for you yet. Working on it myself right now.
Im setting up a flask app on Heroku to set up web hooks for phishing certificates pulling from Facebook's certificate transparency api. I am trying to get pass facebook's verification requests (facebook sending a GET request asking for hub.challenge) however I do not understand how to give them the required information. Before suggesting I use facebook's prebuilt Heroku app, I am doing this to learn.
I tried looking up more information on GET requests however this hasn't helped me solve this problem.
This is facebook's website on this. https://developers.facebook.com/docs/graph-api/webhooks/getting-started#verification-requests
#app.route("/facebook", methods=['GET', 'POST'])
if request.method == 'GET':
def get_facebook(mode, challenge, verify_token):
#not sure what to put in here
After reviewing the docs, a few pointers:
You'll receive the request as a GET, so you won't need the 'POST' value in methods
The values sent from Facebook will be request args, and can be accessed using request.args.get('e.g.')
Facebook is expecting an int to be returned, which is up to you to decide what this is.
The result (disclaimer: this is untested!):
import datetime
from flask import jsonify
#app.route("/facebook", methods=['GET'])
def get_facebook():
my_token = 'abc123' # The token you setup on the App dashboard
if request.args.get('hub.verify_token') == my_token:
# The token is valid, return the (current datetime as an int)
# Assuming facebook is expecting a JSON result value
return jsonify({'hub.challenge': int(datetime.datetime.now().timestamp())})
return 'invalid', 403
I want to authenticate the request coming to my api such that its a valid google account.
#endpoints.api(name='echo',
version='v1',
allowed_client_ids=[API_EXPLORER_CLIENT_ID,
"*****-************.apps.googleusercontent.com"],
auth_level=AUTH_LEVEL.REQUIRED)
class EchoApi(remote.Service):
#endpoints.method(
# This method takes a ResourceContainer defined above.
ECHO_RESOURCE,
# This method returns an Echo message.
EchoResponse,
path='echo',
http_method='POST',
name='echo',
api_key_required=True)
def echo(self, request):
print endpoints.get_current_user()
print request
output_content = ' '.join([request.content] * request.n)
return EchoResponse(content=output_content)
Above is the code that i implemented then also if i send a request without authorization header the request still reaches backend
You must check if get_current_user() returns None.
auth_level does not actually do anything in this version of the Framework.
I'd like to connect a chatbot made in python and deployed on aws lambda to a facebook page.
this is my code to verifly connection to facebook
def webhook(event, context):
# #debug
print("event:" )
print(event)
# print("context")
# print(context)
#handle webhook challenge
if keys_exist(event, ["queryStringParameters"]):
print("Veriflying stuff")
v_token = str(find_item(event, 'hub.verify_token'))
print("token :")
print (v_token)
challenge = int(find_item(event, 'hub.challenge'))
print ("challenge")
print(challenge)
if (os.environ['verify_token'] == v_token):
print ("returning stuff")
return (challenge)
But facebook says The URL couldn't be validated. Callback verification failed with the following errors: HTTP Status Code = 502; HTTP Message = Bad Gateway
I have created the urls with serverless. It works well when i do a get request from the browser.
I have given the same url in the facebook Webhook page. And made sure the validation and Verify Token are correct.
I have tried a few things I saw online. But i dont understand a few of them like this one
Facebook Messenger API "URL COULD NOT BE VALIDATED"
I dont understand if I nned a cert file for this?
AND "The URL could not be validated", facebook api error says to give path to a php. Which I dont even use?
I have found the problem.
Facebook also now required the status code of the request.
if keys_exist(event, ["queryStringParameters","hub.verify_token","hub.challenge"]):
print("subscribe to webhook invoked")
v_token = str(find_item(event, 'hub.verify_token'))
challenge = find_item(event, 'hub.challenge')
if ("strongtoken" == v_token):
response = {
"statusCode": 200,
"body": str(challenge)
}
print(challenge)
return response
this is my first web-programming experience so I hope my questions doesn't sound very dumb. I have been stucked on this for many days.
I am trying to understand a sample code:
https://github.com/facebook/runwithfriends
However I am not understanding very well how the information flow works and how can I modify that sample (i.e. how the code works).
For example, in the following section of the code:
class RecentRunsHandler(BaseHandler):
"""Show recent runs for the user and friends"""
def get(self):
if self.user:
friends = {}
for friend in select_random(
User.get_by_key_name(self.user.friends), 30):
friends[friend.user_id] = friend
self.render(u'runs',
friends=friends,
user_recent_runs=Run.find_by_user_ids(
[self.user.user_id], limit=5),
friends_runs=Run.find_by_user_ids(friends.keys()),
)
else:
self.render(u'welcome')
As I understand (along with HTML) is useful for showing friends that are using the same app, and if I understand correctly, here is the essential part:
*friends_runs=Run.find_by_user_ids(friends.keys())*
But what if I want to show any given friend. How can I do it?
Summarizing, I would like to know:
1- How the flow of the code works? (I don't fully understand the explanation here)
2- How can I manipulate the code so to get, for example, to show a list of friends of the user (not necessary that use the same app)?
Moreover, Can I show friends filtered by some characteristic (for example, gender)?
Thanks a lot!
The python "SDK" for facebook I use I took from https://gist.github.com/1190267
and combined it with the code from the example app to achieve the functionality I wanted both for a canvas app and for website usage.
It depends whether you're using facebook with websites or a canvas application. For a canvas application you probably could do well with the javascript SDK but for a "login with facebook" I required serverside logic that should work with javascript turned off so I've completed that solution with details you might have help to know. You can try make small changes of that specific app 'runwithfriends' to get an understanding which code does what. The project you're looking at contains some outdated practice though:
getting and setting cookies is likely preferable now doing with webapp2's builtin functions for this instead of the code that comes with the FB example app
logging in and out is now done with OAuth 2.0 so it's likely that the login system you're looking at is outdated and you need to use OAuth 2.0 which is described here. I much rather do login/logout serverside so I did an OAuth 2.0 pure python solution to login / logout following the authentication steps mentioned in the tutorial from FB. I had to clear the cookie to log a user out which was not documented.
To upgrade to python 2.7 I had to also modify so that HTTP header did not cast to unicode. I don't know why but otherwise it complained that headers were "not strings"
To more elaborately answer your specific questions:
1) The requesthandler class you posted is a subclass of a BaseHandler so to fully understand what it does you can look at the BaseHandler class since what you are posting is a BAseHandler. The BaseHandler uses django templates for rendering and if you want to can switch the template engine to jinja2 which is remmended. Further the code accesses the user object inherited from the BaseHandler and does some operations on it and renders it to a template. You can try make a requesthandler of your own, subclass BaseHandler and do what you want.
2) I could manipulate the code and I'm not an expert so you should be able to do it too. I wanted a simple FB app to display random images and I could manipulate it to select random images via blobs and render to to a template while keeping the facebook base functions. A function to use for getting the user using the Graph API I do this:
def parse_signed_request(signed_request, secret):
"""
Parse signed_request given by Facebook (usually via POST),
decrypt with app secret.
Arguments:
signed_request -- Facebook's signed request given through POST
secret -- Application's app_secret required to decrpyt signed_request
"""
if '.' in signed_request:
(esig, payload) = signed_request.split('.')
else:
return {}
sig = urlsafe_b64decode(str(esig))
data = _parse_json(urlsafe_b64decode(str(payload)))
if not isinstance(data, dict):
raise SignedRequestError('Pyload is not a json string!')
return {}
if data['algorithm'].upper() == 'HMAC-SHA256':
if hmac.new(secret, payload, hashlib.sha256).digest() == sig:
return data
else:
raise SignedRequestError('Not HMAC-SHA256 encrypted!')
return {}
def get_user_from_cookie(cookies, app_id, app_secret):
"""Parses the cookie set by the official Facebook JavaScript SDK.
cookies should be a dictionary-like object mapping cookie names to
cookie values.
If the user is logged in via Facebook, we return a dictionary with the
keys "uid" and "access_token". The former is the user's Facebook ID,
and the latter can be used to make authenticated requests to the Graph API.
If the user is not logged in, we return None.
Download the official Facebook JavaScript SDK at
http://github.com/facebook/connect-js/. Read more about Facebook
authentication at http://developers.facebook.com/docs/authentication/.
"""
cookie = cookies.get('fbsr_' + app_id, '')
if not cookie:
return None
response = parse_signed_request(cookie, app_secret)
if not response:
return None
args = dict(code=response['code'], client_id=app_id,
client_secret=app_secret, redirect_uri='')
file = \
urllib.urlopen('https://graph.facebook.com/oauth/access_token?'
+ urllib.urlencode(args))
try:
token_response = file.read()
finally:
file.close()
access_token = cgi.parse_qs(token_response)['access_token'][-1]
logging.debug('returning cookie')
return dict(uid=response['user_id'], access_token=access_token)
See http://developers.facebook.com/docs/api for complete documentation for the API. And you can get the the official Facebook JavaScript SDK at http://github.com/facebook/connect-js/
I'm now writing code to sync a webapp2_extras.auth account with facebook so that custom accounts and facebook accounts can co-exist and we're discussing solutions for this in the webapp2 groups and categories. The current way I do it is adding the recommended current_user to a basehandler and using that as the FB identity while working on "merging" my class FBUser that is a custom class for facebook users that autheorized my website and/or canvas application to sync with webapp2_extras.auth.models.User which is an expando model so it can just add the properties it doesn't have such as facebookid, firstname, lastname, etc.
#property
def current_user(self):
if not hasattr(self, '_current_user'):
self._current_user = None
cookie = get_user_from_cookie(self.request.cookies,
facebookconf.FACEBOOK_APP_ID,
facebookconf.FACEBOOK_APP_SECRET)
if cookie:
# Store a local instance of the user data so we don't need
# a round-trip to Facebook on every request
user = FBUser.get_by_key_name(cookie['uid'])
if not user:
graph = GraphAPI(cookie['access_token'])
profile = graph.get_object('me')
user = FBUser(key_name=str(profile['id']),
id=str(profile['id']),
name=profile['name'],
profile_url=profile['link'],
access_token=cookie['access_token'])
user.put()
elif user.access_token != cookie['access_token']:
user.access_token = cookie['access_token']
user.put()
self._current_user = user
return self._current_user
You can also solve your authentication with session objects and build your authentication system around that. That is what I do when using both custom accounts and facebook accounts and you're welcome to have a lok at my repository for more code examples how to intregrate facebook with google app engine using python 2.7.