This is a collection of drop-in Python Flask views for the initial OAuth client flows for many popular sites, including Blogger, Disqus, Dropbox, Facebook, Flickr, GitHub, Google, IndieAuth, Instagram, LinkedIn, Mastodon, Medium, Tumblr, Twitter, and WordPress.com.
Available on PyPi. Install with
pip install oauth-dropins.
Demo app at oauth-dropins.appspot.com.
This software is released into the public domain. See LICENSE for details.
Here’s a full example of using the GitHub drop-in.
Install oauth-dropins with
pip install oauth-dropins.
Put your GitHub OAuth application’s ID and secret in two plain text files in your app’s root directory,
github_client_secret. (If you use git, you’ll probably also want to add them to your
github_oauth.pyfile with these contents:
from oauth_dropins import github from app import app # ...or wherever your Flask app is app.add_url_rule('/start', view_func=github.Start.as_view('start', '/callback'), methods=['POST']) app.add_url_rule('/callback', view_func=github.Callback.as_view('callback', '/after'))
Voila! Send your users to
/github/start when you want them to
connect their GitHub account to your app, and when they’re done, they’ll
be redirected to
/after?access_token=... in your app.
All of the sites provide the same API. To use a different one, just
import the site module you want and follow the same steps. The filenames
for app keys and secrets also differ by site; see each site’s
file for its filenames.
There are three main parts to an OAuth drop-in: the initial redirect to
the site itself, the redirect back to your app after the user approves
or declines the request, and the datastore entity that stores the user’s
OAuth credentials and helps you use them. These are implemented by
`Callback <#callback>`__, which are
classes, and auth entities, which are Google Cloud
This view class redirects you to an OAuth-enabled site so it can ask the user to grant your app permission. It has two useful methods:
__init__(self, to_path, scopes=None).
to_pathis the OAuth callback, ie URL path on your site that the site’s OAuth flow should redirect back to after it’s done. This is handled by a Callback view in your application, which needs to handle the
If you want to add OAuth scopes beyond the default one(s) needed for login, you can pass them to the
scopeskwarg as a string or sequence of strings, or include them in the
scopesquery parameter in the POST request body. This is supported in most sites, but not all.
Some OAuth 1 sites support alternatives to scopes. For Twitter, the
Startconstructor takes an additional
access_typekwarg that may be
write. It’s passed through to Twitter x_auth_access_type. For Flickr,
permsPOST query parameter that may be
delete; it’s passed through to Flickr unchanged. (Flickr claims it’s optional, but sometimes breaks if it’s not provided.)
redirect_url(state=None)returns the URL to redirect to at the site to initiate the OAuth flow.
Startwill redirect here automatically if it’s used in a WSGI application, but you can call this manually if you want to control that redirect yourself:
import flask class MyView(Start): def dispatch_request(self): ... flask.redirect(self.redirect_url())
This class handles the HTTP redirect back to your app after the user has granted or declined permission. It also has two useful methods:
__init__(self, to_path, scopes=None).
to_pathis the URL path on your site that users should be redirected to after the callback view is done. It will include a
statequery parameter with the value provided to
Start. It will also include an OAuth token in its query parameters, either
access_tokenfor OAuth 2.0 or
access_token_secretfor OAuth 1.1. It will also include an
auth_entityquery parameter with the string key of an auth entity that has more data (and functionality) for the authenticated user. If the user declined the OAuth authorization request, the only query parameter besides
finish(auth_entity, state=None)is run in the initial callback request after the OAuth response has been processed.
auth_entityis the newly created auth entity for this connection, or
Noneif the user declined the OAuth authorization request.
to_path, but you can subclass
Callbackand override it to run your own code instead of redirecting:
class MyCallback(github.Callback): def finish(self, auth_entity, state=None): super().finish(auth_entity, state=state) # ignore returned redirect self.response.write('Hi %s, thanks for connecting your %s account.' % (auth_entity.user_display_name(), auth_entity.site_name()))
Each site defines an App Engine datastore ndb.Model
that stores each user’s OAuth credentials and other useful information,
like their name and profile URL. The class name is generally of the form
GitHubAuth. Here are the useful methods:
site_name()returns the human-readable string name of the site, e.g. “Facebook”.
user_display_name()returns a human-readable string name for the user, e.g. “Ryan Barrett”. This is usually their first name, full name, or username.
access_token()returns the OAuth access token. For OAuth 2 sites, this is a single string. For OAuth 1.1 sites (currently just Twitter, Tumblr, and Flickr), this is a
(string key, string secret)tuple.
The following methods are optional. Auth entity classes usually implement at least one of them, but not all.
urlopen()and adds the OAuth credentials to the request. Use this for making direct HTTP request to a site’s REST API. Some sites may provide
get()instead, which wraps
If you get this error:
bash: ./bin/easy_install: ...bad interpreter: No such file or directory
You’ve probably hit this virtualenv bug: virtualenv doesn’t support paths with spaces.
The easy fix is to recreate the virtualenv in a path without spaces. If
you can’t do that, then after creating the virtualenv, but before
activating it, edit the activate, easy_install and pip files in
local/bin/ to escape any spaces in the path.
For example, in
VIRTUAL_ENV=".../has\ space/local", and in
easy_install the first line changes from
#!".../has space/local/bin/python" to
This should get virtualenv to install in the right place. If you do this
wrong at first, you’ll have installs in eg
/usr/local/lib/python3.7/site-packages that you need to delete,
since they’ll prevent virtualenv from installing into the local
If you see errors importing or using
tweepy, it may be because
six.pyisn’t installed. Try
pip install sixmanually.
sixin its dependencies, so this shouldn’t be necessary. Please let us know if it happens to you so we can debug!
If you get an error like this:
Running setup.py develop for gdata ... error: option --home not recognized ... InstallationError: Command /usr/bin/python -c "import setuptools, tokenize; __file__='/home/singpolyma/src/bridgy/src/gdata/setup.py'; exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" develop --no-deps --home=/tmp/tmprBISz_ failed with error code 1 in .../src/gdata
…you may be hitting Pip bug
1833. Are you passing
pip install? Use the virtualenv instead, it’s your friend.
If you really want
-t, try removing the
-e from the lines in
requirements.txt that have it.
6.1 - 2023-03-22
Store access token and refresh token in
6.0 - 2022-12-03
Drop Python 3.6 support. Python 3.7 is now the minimum required version.
Fix bug when user approves the OAuth prompt but has no Blogger blogs. Instead of crashing, we now redirect to the callback with
declined=True, which is still wrong, but less bad.
StringPropertyso that it’s indexed in the Datastore.
When the callback gets an invalid
stateparameter, return HTTP 400 instead of raising
Misc webutil updates.
5.0 - 2022-03-23
Drop Python 3.5 support. Python 3.6 is now the minimum required version.
Switch from app_server to
flask runfor local development.
User-Agentheader to be sent with all HTTP requests.
4.0 - 2021-09-15
to()class methods. Instead, now pass redirect paths to Flask’s
app = Flask() app.add_url_rule('/start', view_func=twitter.Callback.as_view('start', '/oauth_callback'))
webutil: migrate webapp2 HTTP request handlers in the
HostMetaXrdsHandler- to Flask views in a new
webutil: implement Webmention protocol in new
webutil: add misc Flask utilities and helpers in new
3.1 - 2021-04-03
3.0 - 2020-03-14
handlers.memcache_response(), which used Python 2 App Engine’s memcache service, with
cache_response(), which uses local runtime memory.
handlers.TemplateHandler.USE_APPENGINE_WEBAPPtoggle to use Python 2 App Engine’s
google.appengine.ext.webapp2.templateinstead of Jinja.
Login is now based on Google Sign-In. The
http()methods have been removed. Use the remaining
api()method to get a
access_token()to make API calls manually.
GoogleAuthwith the new
GoogleUserNDB model class, which doesn’t depend on the deprecated oauth2client.
http()method (which returned an
APP_URLclass attributes and
app_urlkwargs in the
to()method and replace them with new
app_url()methods that subclasses should override, since they often depend on WSGI environment variables like
SERVER_NAMEthat are available during requests but not at runtime startup.
handlers.memcache_response()since the Python 3 runtime doesn’t include memcache.
USE_APPENGINE_WEBAPP, since the Python 3 runtime doesn’t include
util.follow_redirects(). Caching is now built in. You can bypass the cache with
Add Meetup support. (Thanks Jamie Tanna!)
statequery parameter now works!
button_html()for the outer
<div>, eg as Bootstrap columns.
2.2 - 2019-11-01
Add LinkedIn and Mastodon!
Add Python 3.7 support, and improve overall Python 3 compatibility.
button_html()method to all
StartHandlerclasses. Generates the same button HTML and styling as on oauth-dropins.appspot.com.
Blogger: rename module from
blogger_v2module name is still available as an alias, implemented via symlink, but is now deprecated.
Dropbox: fix crash with unicode header value.
Google: fix crash when user object doesn’t have
Update a number of dependencies.
Switch from Python’s built in
jsonmodule to ujson (built into App Engine) to speed up JSON parsing and encoding.
2.0 - 2019-02-25
Breaking change: switch from Google+ Sign-In (which shuts down in March) to Google Sign-In. Notably, this removes the
googleplusmodule and adds a new
google_signinmodule, renames the
GoogleAuth, and removes its
api()method. Otherwise, the implementation is mostly the same.
webutil.logs: return HTTP 400 if
start_timeis before 2008-04-01 (App Engine’s rough launch window).
1.14 - 2018-11-12
1.13 - 2018-08-08
IndieAuth: support JSON code verification responses as well as form-encoded (snarfed/bridgy#809).
1.12 - 2018-03-24
More Python 3 updates and bug fixes in webutil.util.
1.11 - 2018-03-08
stateto the initial OAuth endpoint directly, instead of encoding it into the redirect URL, so the redirect can match the Strict Mode whitelist.
Add Python 3 support to webutil.util!
Add humanize dependency for webutil.logs.
1.10 - 2017-12-10
Mostly just internal changes to webutil to support granary v1.10.
1.9 - 2017-10-24
Mostly just internal changes to webutil to support granary v1.9.
Handle punctuation in error messages.
1.8 - 2017-08-29
Upgrade Graph API from v2.6 to v2.10.
Bug fix for Medium OAuth callback error handling.
Store authorization endpoint in state instead of rediscovering it from
meparameter, which is going away.
1.7 - 2017-02-27
Updates to bundled webutil library, notably WideUnicode class.
1.6 - 2016-11-21
Add auto-generated docs with Sphinx. Published at oauth-dropins.readthedocs.io.
Fix Dropbox bug with fetching access token.
1.5 - 2016-08-25
1.4 - 2016-06-27
Upgrade Facebook API from v2.2 to v2.6.
1.3 - 2016-04-07
More consistent logging of HTTP requests.
Set up Coveralls.
1.2 - 2016-01-11
Add upload method.
Improve error handling and logging.
Bug fixes and cleanup for constructing scope strings.
Add developer setup and troubleshooting docs.
Set up CircleCI.
1.1 - 2015-09-06
Flickr: split out flickr_auth.py file.
Add a number of utility functions to webutil.
1.0 - 2015-06-27
Initial PyPi release.
Pull requests are welcome! Feel free to ping me in #indieweb-dev with any questions.
First, fork and clone this repo. Then, install the Google Cloud
SDK and run
gcloud components install beta cloud-datastore-emulator to install
Then, set up your environment by running these commands in the repo root
directory. Once you have them, set up your environment by running these
commands in the repo root directory:
gcloud config set project oauth-dropins git submodule init git submodule update python3 -m venv local source local/bin/activate pip install -r requirements.txt
Run the demo app locally with flask run:
gcloud beta emulators datastore start --use-firestore-in-datastore-mode --no-store-on-disk --host-port=localhost:8089 --quiet < /dev/null >& /dev/null & GAE_ENV=localdev FLASK_ENV=development flask run -p 8080
To deploy to production:
gcloud -q beta app deploy --no-cache oauth-dropins *.yaml
The docs are built with Sphinx, including
Configuration is in
To build them, first install Sphinx with
pip install sphinx. (You
may want to do this outside your virtualenv; if so, you’ll need to
reconfigure it to see system packages with
python3 -m venv --system-site-packages local.) Then, run
Here’s how to package, test, and ship a new release. (Note that this is largely duplicated in granary’s readme too.)
Run the unit tests.
sh source local/bin/activate.csh gcloud beta emulators datastore start --use-firestore-in-datastore-mode --no-store-on-disk --host-port=localhost:8089 < /dev/null >& /dev/null & sleep 2s DATASTORE_EMULATOR_HOST=localhost:8081 DATASTORE_DATASET=oauth-dropins \ python3 -m unittest discover kill %1 deactivate
Bump the version number in
git grepthe old version number to make sure it only appears in the changelog. Change the current changelog entry in
README.mdfor this new version from unreleased to the current date.
Build the docs. If you added any new modules, add them to the appropriate file(s) in
docs/source/. Then run
git commit -am 'release vX.Y'
Upload to test.pypi.org for testing.
sh python3 setup.py clean build sdist setenv ver X.Y source local/bin/activate.csh twine upload -r pypitest dist/oauth-dropins-$ver.tar.gz
Install from test.pypi.org.
sh cd /tmp python3 -m venv local source local/bin/activate.csh pip3 install --upgrade pip # mf2py 1.1.2 on test.pypi.org is broken :( pip3 install mf2py pip3 install -i https://test.pypi.org/simple --extra-index-url https://pypi.org/simple oauth-dropins deactivate
Smoke test that the code trivially loads and runs.
sh source local/bin/activate.csh python3 # run test code below deactivateTest code to paste into the interpreter:
py from oauth_dropins.webutil import util util.__file__ util.UrlCanonicalizer()('http://asdf.com') # should print 'https://asdf.com/' exit()
Tag the release in git. In the tag message editor, delete the generated comments at bottom, leave the first line blank (to omit the release “title” in github), put
### Notable changeson the second line, then copy and paste this version’s changelog contents below it.
sh git tag -a v$ver --cleanup=verbatim git push git push --tags
Click here to draft a new release on GitHub. Enter
vX.Yin the Tag version box. Leave Release title empty. Copy
### Notable changesand the changelog contents into the description text box.
Upload to pypi.org!
sh twine upload dist/oauth-dropins-$ver.tar.gz