A collection of utilities for integrating User Accounts with Facebook.com.
Right now this library is focused on oAuth Login and Graph API operations
This library has been a general-purpose Python client, operating in production for many years. The functionality may be limited, but it works well and ships with a detailed test suite.
-
Once upon a time, Facebook offered a Python SDK. They dropped it. Boo.
-
When this was first released, there were no other Python SDKs actively developed.
-
There are a handful of Pyramid/Misc Framework utilities that provide a complete drop-in integration with Facebook.com for account logins and integrations; This is NOT one of them. Sometimes you need to control the User Experience and have all your UX customized; if so, this is for you.
All work is done via the facebook_utils.core.FacebookHub()
object.
Configure a hub with something like the following:
from facebook_utils import FacebookHub
hub = FacebookHub(app_id = x,
app_secret = y,
app_secretproof = True
)
Or make it unuthenticated. It's up to you.
This was originally built/intended for use under the Pyramid environment.
Calling FacebookPyramid()
will create a new object that subclasses
FacebookHub()
objects, using default settings from your .ini and
pulling variables from request
as needed.
As of v0.5.0 it supports the appsecret_proof
lockdown on the client level.
Any requests to the hub will attempt to create the appsecret_proof
hmac if it
is not explicitly provided. It will be based on the access_token
appearing as
(in the order of precedence):
- the
access_token
kwarg to the `.api_proxy() method - an
access_token
in the querystring of a retrieved url - an
access_token
in the POST payload
This will allow you to follow paginated links from the API as-is, upgrading as needed.
Facebook's API Support is inconsistent with the terminology:
- The API endpoints expect
client_id
andclient_secret
- The Developer Tools provide
app id
andapp secret
For the sake of clarity, this library uses the terms app_id
and app_secret
because they are what Facebook's developer dashboard provides. They are translated
into the API Endpoint terms as required.
By default the API calls will be unversioned. You should specify the API version.
- configure an object with
oauth_code_redirect_uri
- consumers click a button on your site, which redirects to
oauth_code_redirect_uri
-- as provided byoauth_code__url_dialog()
- upon success, users are redirected from Facebook to
oauth_code_redirect_uri
along with a query param titledcode
- you may then call
.oauth_code__get_access_token()
to get an access token or calloauth_code__get_access_token_and_profile()
to get the token and profile data. - profile data can be updated with
.graph__get_profile(access_token)
- configure an object with
oauth_token_redirect_uri
- consumers click a button on your site, which redirects to
oauth_token_redirect_uri
-- as provided byoauth_token__url_dialog()
- upon success, users are redirected from Facebook to
oauth_token__url_dialog
along with a query param titledtoken
and a hash value titled#access_token
. Theaccess_token
is not visible to the server, and must be transferred to your server via JavaScript or not-at-all should you simply want to do all your integration in JavaScript. - profile data can be obtained with
.graph__get_profile(access_token)
if you store the access token
Queries without authentication required can be performed using the .api_proxy
method.
If you don't have any authentication data, you can create an unauthenticated hub, which allows you to leverage this library for streamlined requests and response processing.
hub = FacebookHub(unauthenticated_hub=True)
get_data = {'ids': "http://example.com", }
fb_data = hub.api_proxy(url="""https://graph.facebook.com""",
expected_format='json.load', get_data=get_data)
pprint.pprint(fb_data)
Facebook may ratelimit requests.
The last response is stored FacebookHub._last_response
for inspection.
A convenience method will check for the X-Page-Usage
ratelimiting header:
print hub.last_response_ratelimited()
If no ratelimiting is set, it will return None.
If Facebook has set ratelimiting, it will convert the JSON-formatted string in the header into a python dict:
print hub.last_response_ratelimited()
> {"call_count" : x,
"total_time" : y,
"total_cputime" : z
}
Most methods will let you override the scope
and request_uri
.
This shouldn't really be necessary and will probably be deprecated.
Some methods support multiple ways of parsing results. Until recently, Facebook's API returned values either as url-encoded strings or as JSON. Now most results are in JSON.
define some variables in your .ini files:
file: development.ini
# the default prefix is fbutils
fbutils.id = 123
fbutils.secret = 123
fbutils.scope = email, user_birthday, user_checkins, offline_access
fbutils.oauth_code_redirect_uri = http://127.0.0.1:5010/facebook-oauth-redirect
fbutils.api_version = v2.8
fbutils.oauth_code_redirect_uri= http://127.0.0.1:5010/account/facebook-authenticate-oauth?response_type=code
fbutils.oauth_token_redirect_uri= http://127.0.0.1:5010/account/facebook-authenticate-oauth-token?response_type=token
or:
# customize the prefix!
fbutils.prefix = facebook.app
facebook.app.id = 123
facebook.app.secret = 123
facebook.app.secretproof = True
facebook.app.scope = email, user_birthday, user_checkins, offline_access
facebook.app.oauth_code_redirect_uri = http://127.0.0.1:5010/facebook-oauth-redirect
facebook.app.api_version = v2.8
facebook.app.oauth_code_redirect_uri= http://127.0.0.1:5010/account/facebook-authenticate-oauth?response_type=code
facebook.app.oauth_token_redirect_uri= http://127.0.0.1:5010/account/facebook-authenticate-oauth-token?response_type=token
Make sure your endpoints are allowlisted on the Facebook console
Integrate into your views:
from facebook_utils import FacebookPyramid
class WebAccount(base.Handler):
def __new_fb_object(self):
"Create a new Facebook Object
note that we can override settings in the .ini files if we want
"
fb = FacebookPyramid(self.request,
oauth_code_redirect_uri = self.request.registry.settings['facebook.app.oauth_code_redirect_uri'],
oauth_token_redirect_uri = self.request.registry.settings['facebook.app.oauth_token_redirect_uri'],
)
return fb
def sign_up(self):
"sign up page, which contains a "signup with facebook link"
fb = self.__new_fb_object()
return {"project":"MyApp", 'facebook_pyramid':facebook}
@action(renderer="web/account/facebook_authenticate_oauth.html")
def facebook_authenticate_oauth(self):
fb = self.__new_fb_object()
(access_token,
profile
)= fb.oauth_code__get_access_token_and_profile(self.request)
if profile:
# congrats, they logged in
# register the user to your db
raise HTTPFound(location='/account/home')
else:
# boo, that didn't work
raise HTTPFound(location='/account/sign-up?error=facebook-oauth-failure')
return {"project":"MyApp"}
Integrate into your template:
<a class="fancy_button-1" id="signup-start_btn-facebook" href="${facebook_pyramid.oauth_code__url_dialog()}">
Connect with <strong>Facebook</strong>
</a>
Every hub
object has an api_proxy
method, which can be used to
centralize communication to the Facebook API
Facebook's API isn't very 'standardized' across the board. Some endpoints
return json data, others return urlquoted data. api_proxy
doesn't care.
it returns a dict from every endpoint, and does the conversion for you.
The api_proxy
defaults to a json load. certain api calls will pass in
a different expected_format
argument. The proxy will also handle "batch"
style graph requests.
When the api_proxy encounters an error, it returns ApiError
or a more
contextual subclass of the that exception class.
The current exception class inheritance is:
ApiError
ApiAuthError
ApiAuthExpiredError
ApiApplicationError
ApiResponseError
ApiRuntimeError
ApiRuntimeVerirficationFormatError
ApiRuntimeGrantError
ApiRuntimeScopeError
ApiRuntimeGraphMethodError
ApiUnhandledError
AuthenticatedHubRequired
ApiError
instances contain:
code (Facebook specific, not http code)
type (as dictacted by Facebook)
message (possibly dictated by Facebook)
raised (the trapped error that raised this, if available)
response (the repsonse in error, if available)
AuthenticatedHubRequired
will be raised if a non-authenticated hub tries to
perform authenticated actions
The api_proxy
will catch most errors. Since the Facebook API is in constant
flux, uncaught exceptions are raised. There will be a future "ApiUnhandledError".
-
Set up an App on https://developers.facebook.com
-
You need this information exported in your environment
FBUTILS_APP_ID : The numeric ID for the app FBUTILS_APP_SECRET : You generate this on the Facebook Settings FBUTILS_APP_SECRETPROOF=1 : FBUTILS_APP_DOMAIN : the App's domain. example: dev.example.com FBUTILS_APP_SCOPE : The scope. can just be "email" FBUTILS_REDIRECT_URI_OAUTH_CODE : the uri to redirect visitors to. this MUST be whitelisted on Facebook's OAuth/Login settings
-
Run
python test-interactive.py
You will see a message like:
Visit the following url to approve. You will be redirected back to the `FBUTILS_REDIRECT_URI_OAUTH_CODE` URI
https://www.facebook.com/dialog/oauth?client_id={XXXXX}&scope={XXXXX}&redirect_uri={XXXXX}
Copy/paste that url into a browser window
You will be redirected to a url like:
{{https://dev.cliqued.in/account/login/facebook-oauth?response_type=code}}&code={{CODE}}#_=_
Copy the entire CODE
from the URL and paste it in. It is okay to leave the
trailing #_=_
fragment
This will step you through multiple API calls, asking you to visit the authorization URL each time.
You should notice the authorization code changes each time, however the Facebook API token which is returned will remain the same.
Unit Tests require the following environment vars to be set:
FBUTILS_APP_ID
FBUTILS_APP_SECRET
FBUTILS_APP_SECRETPROOF
FBUTILS_APP_SCOPE
FBUTILS_ACCESS_TOKEN
FBUTILS_APP_DOMAIN
it should be simple...
export FBUTILS_APP_ID="app_id_from_facebook.com"
export FBUTILS_APP_SECRET="app_secret_from_facebook.com"
export FBUTILS_APP_SECRETPROOF=set if you locked this down on facebook
export FBUTILS_APP_SCOPE="email,user_activities,user_status,user_posts"
export FBUTILS_APP_DOMAIN='allowlisted domain'
export FBUTILS_REDIRECT_URI_OAUTH_CODE= configured on the facebook dashboard
export FBUTILS_ACCESS_TOKEN="from_API_operations, or generate via developer interface"
To generate a FBUTILS_ACCESS_TOKEN
value, you can use the tests/generate_credentials.py
script.
Managed by Github Secrets
FBUTILS_APP_SECRET
FBUTILS_ACCESS_TOKEN
Set in .github/workflows/python-package.yml
FBUTILS_APP_ID
FBUTILS_APP_SECRETPROOF
FBUTILS_APP_SCOPE
FBUTILS_APP_DOMAIN
FBUTILS_REDIRECT_URI_OAUTH_CODE
FBUTILS_REDIRECT_URI_OAUTH_TOKEN
There is also a test_interactive.py
file that uses the same environment vars
python test_interactive.py
That will allow you to step through a few scenarios and set up an integration with Facebook itself.
©️ 2012-2021 by Jonathan Vanasco license: BSD