Package: oauth-dropins
oauth-dropinsDrop-in PythonOAuthfor popular sites!AboutQuick startUsage detailsTroubleshooting/FAQChangelogDevelopmentRelease instructionsAboutThis is a collection of drop-in PythonFlaskviews for the initialOAuthclient flows for many popular sites, including Blogger, Disqus, Dropbox, Facebook, Flickr, GitHub, Google, IndieAuth, Instagram, LinkedIn, Mastodon, Medium, Tumblr, Twitter, and WordPress.com.oauth-dropins stores user credentials inGoogle Cloud Datastore. It's primarily designed forGoogle App Engine, but it can be used in any Python web application, regardless of host or framework.Available on PyPi.Install withpip install oauth-dropins.Getting started docs.Reference docs.Demo app atoauth-dropins.appspot.com.Source code on GitHub.This software is released into the public domain. See LICENSE for details.Quick startHere's a full example of using the GitHub drop-in.Install oauth-dropins withpip install oauth-dropins.Put yourGitHub OAuth application'sID and secret in two plain text files in your app's root directory,github_client_idandgithub_client_secret. (If you use git, you'll probably also want to add them to your.gitignore.)Create agithub_oauth.pyfile with these contents:fromoauth_dropinsimportgithubfromappimportapp# ...or wherever your Flask app isapp.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/startwhen 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.pyfile for its filenames.Usage detailsThere 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 byStartandCallback, which areFlaskViewclasses, andauth entities, which areGoogle Cloud Datastorendb models.StartThis 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:The constructor,__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 aCallbackview in your application, which needs to handle theto_pathroute.If you want to add OAuth scopes beyond the default one(s) needed for login, you can pass them to thescopeskwarg as a string or sequence of strings, or include them in thescopesquery 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, theStartconstructor takes an additionalaccess_typekwarg that may bereadorwrite. It's passed through to Twitterx_auth_access_type. For Flickr,Startaccepts apermsPOST query parameter that may beread,writeordelete; it'spassed through to Flickrunchanged. (Flickr claims it's optional, butsometimes 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:importflaskclassMyView(Start):defdispatch_request(self):...flask.redirect(self.redirect_url())CallbackThis class handles the HTTP redirect back to your app after the user has granted or declined permission. It also has two useful methods:The constructor,__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 astatequery parameter with the value provided toStart. It will also include an OAuth token in its query parameters, eitheraccess_tokenfor OAuth 2.0 oraccess_token_keyandaccess_token_secretfor OAuth 1.1. It will also include anauth_entityquery parameter with the string key of anauth entitythat has more data (and functionality) for the authenticated user. If the user declined the OAuth authorization request, the only query parameter besidesstatewill bedeclined=true.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, orNoneif the user declined the OAuth authorization request.By default,finishredirects toto_path, but you can subclassCallbackand override it to run your own code instead of redirecting:classMyCallback(github.Callback):deffinish(self,auth_entity,state=None):super().finish(auth_entity,state=state)# ignore returned redirectself.response.write('Hi%s, thanks for connecting your%saccount.'%(auth_entity.user_display_name(),auth_entity.site_name()))Auth entitiesEach site defines an App Engine datastorendb.Model classthat stores each user's OAuth credentials and other useful information, like their name and profile URL. The class name is generally of the formSiteAuth, e.g.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.api()returns a site-specific API object. This is usually a third party library dedicated to the site, e.g.Tweepyorpython-instagram. See the site class's docstring for details.urlopen(data=None, timeout=None)wrapsurlopen()and adds the OAuth credentials to the request. Use this for making direct HTTP request to a site's REST API. Some sites may provideget()instead, which wrapsrequests.get().Troubleshooting/FAQIf you get this error:bash: ./bin/easy_install: ...bad interpreter: No such file or directoryYou've probably hitthis 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 inlocal/bin/to escape any spaces in the path.For example, inactivate,VIRTUAL_ENV=".../has space/local"becomesVIRTUAL_ENV=".../has\ space/local", and inpipandeasy_installthe first line changes from#!".../has space/local/bin/python"to#!".../has\ space/local/bin/python".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-packagesthat you need to delete, since they'll prevent virtualenv from installing into the localsite-packages.If you see errors importing or usingtweepy, it may be becausesix.pyisn't installed. Trypip install sixmanually.tweepydoes includesixin its dependencies, so this shouldn't be necessary. Pleaselet us knowif 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 hittingPip bug 1833. Are you passing-ttopip install? Use the virtualenv instead, it's your friend. If you really want-t, try removing the-efrom the lines inrequirements.txtthat have it.Changelog6.2 - 2023-09-15Miscellaneous changes inwebutil.6.1 - 2023-03-22Non-breaking changes:IndieAuth:Store access token and refresh token inIndieAuthdatastore entities.Flickr:Handle errors from initial OAuth 1.0 authorization request.6.0 - 2022-12-03Breaking changes:Removewebutil.handlers, which was based on the largely unmaintainedwebapp2. All known clients have migrated toFlaskandwebutil.flask_util.Drop Python 3.6 support. Python 3.7 is now the minimum required version.Non-breaking changes:Add newtwitter_v2module for Twitter's newOAuth 2 with PKCEsupport andv2 API.IndieAuth:Add support forauthorization endpoints, along with existingtoken endpointsupport. Thanks@jamietanna! (#284)Blogger:Fix bug when user approves the OAuth prompt but has no Blogger blogs. Instead of crashing, we now redirect to the callback withdeclined=True, which is still wrong, but less bad.Mastodon:ChangeMastodonAuth.access_token_strfrom ndbTextPropertytoStringPropertyso that it's indexed in the Datastore.When the callback gets an invalidstateparameter, return HTTP 400 instead of raisingJSONDecodeError.Misc webutil updates.5.0 - 2022-03-23Breaking changes:Drop Python 3.5 support. Python 3.6 is now the minimum required version.Non-breaking changes:Switch from app_server toflask runfor local development.Addwebutil.util.set_user_agentto setUser-Agentheader to be sent with all HTTP requests.4.0 - 2021-09-15Breaking changes:Migrate fromwebapp2toFlask. webapp2 had a good run, but it's no longer actively developed, and Flask is one of the most widely adopted standalone web frameworks in the Python community.Removeto()class methods. Instead, now pass redirect paths to Flask'sas_view()function, eg:app=Flask()app.add_url_rule('/start',view_func=twitter.Callback.as_view('start','/oauth_callback'))Remove deprecatedblogger_v2module alias.webutil: migrate webapp2 HTTP request handlers in thehandlersmodule -XrdOrJrdHandler,HostMetaHandler, andHostMetaXrdsHandler- to Flask views in a newflask_utilmodule.Non-breaking changes:webutil: implementWebmentionprotocol in newwebmentionmodule.webutil: add misc Flask utilities and helpers in newflask_utilmodule.3.1 - 2021-04-03Add Python 3.8 support, drop 3.3 and 3.4. Python 3.5 is now the minimum required version.AddPixelfedsupport, heavily based on Mastodon.AddRedditsupport. ThanksWill Stedden!WordPress.com:Handle errors from access token request.3.0 - 2020-03-14Breaking changes:Python 2 is no longer supported!Including theApp Engine Standard Python 2 runtime. On the plus side, thePython 3 runtimes, bothStandardandFlexible, are now supported.Replacehandlers.memcache_response(), which used Python 2 App Engine's memcache service, withcache_response(), which uses local runtime memory.Remove thehandlers.TemplateHandler.USE_APPENGINE_WEBAPPtoggle to use Python 2 App Engine'sgoogle.appengine.ext.webapp2.templateinstead of Jinja.Blogger:Login is now based onGoogle Sign-In. Theapi_from_creds(),creds(), andhttp()methods have been removed. Use the remainingapi()method to get aBloggerClient, oraccess_token()to make API calls manually.Google:ReplaceGoogleAuthwith the newGoogleUserNDB model class, whichdoesn't depend on the deprecated oauth2client.Drophttp()method (which returned anhttplib2.Http).Mastodon:StartHandler: dropAPP_NAME/APP_URLclass attributes andapp_name/app_urlkwargs in theto()method and replace them with newapp_name()/app_url()methods that subclasses should override, since they often depend on WSGI environment variables likeHTTP_HOSTandSERVER_NAMEthat are available during requests but not at runtime startup.webutil:Drophandlers.memcache_response()since the Python 3 runtime doesn't include memcache.Drophandlers.TemplateHandlersupport forwebapp2.templateviaUSE_APPENGINE_WEBAPP, since the Python 3 runtime doesn't includewebapp2built in.Removecacheandfail_cache_time_secskwargs fromutil.follow_redirects(). Caching is now built in. You can bypass the cache withfollow_redirects.__wrapped__().Details.Non-breaking changes:Add Meetup support. (ThanksJamie Tanna!)Blogger, Google:Thestatequery parameter now works!Add newouter_classeskwarg tobutton_html()for the outer<div>, eg as Bootstrap columns.Add newimage_filekwarg toStartHandler.button_html()2.2 - 2019-11-01Add LinkedIn and Mastodon!Add Python 3.7 support, and improve overall Python 3 compatibility.Add newbutton_html()method to allStartHandlerclasses. Generates the same button HTML and styling as onoauth-dropins.appspot.com.Blogger: rename module fromblogger_v2toblogger. Theblogger_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 havenamefield.Facebook:upgrade Graph API version from 2.10 to 4.0.Update a number of dependencies.Switch from Python's built injsonmodule toujson(built into App Engine) to speed up JSON parsing and encoding.2.0 - 2019-02-25Breaking change: switch fromGoogle+ Sign-In(which shuts down in March) toGoogle Sign-In. Notably, this removes thegoogleplusmodule and adds a newgoogle_signinmodule, renames theGooglePlusAuthclass toGoogleAuth, and removes itsapi()method. Otherwise, the implementation is mostly the same.webutil.logs: return HTTP 400 ifstart_timeis before 2008-04-01 (App Engine's rough launch window).1.14 - 2018-11-12Fix dev_appserver in Cloud SDK 219 /app-engine-python1.9.76 and onward.Background.Upgradegoogle-api-python-clientfrom 1.6.3 to 1.7.4 tostop using the global HTTP Batch endpoint.Other minor internal updates.1.13 - 2018-08-08IndieAuth: support JSON code verification responses as well as form-encoded (snarfed/bridgy#809).1.12 - 2018-03-24More Python 3 updates and bug fixes in webutil.util.1.11 - 2018-03-08Add GitHub!Facebook:Passstateto the initial OAuth endpoint directly, instead of encoding it into the redirect URL, so the redirect canmatch the Strict Mode whitelist.Add Python 3 support to webutil.util!Add humanize dependency for webutil.logs.1.10 - 2017-12-10Mostly just internal changes to webutil to support granary v1.10.1.9 - 2017-10-24Mostly just internal changes to webutil to support granary v1.9.Flickr:Handle punctuation in error messages.1.8 - 2017-08-29Facebook:Upgrade Graph API from v2.6 to v2.10.Flickr:Fix brokenFlickrAuth.urlopen()method.Medium:Bug fix for Medium OAuth callback error handling.IndieAuth:Store authorization endpoint in state instead of rediscovering it frommeparameter,which is going away.1.7 - 2017-02-27Updates to bundled webutil library, notably WideUnicode class.1.6 - 2016-11-21Add auto-generated docs with Sphinx. Published atoauth-dropins.readthedocs.io.Fix Dropbox bug with fetching access token.1.5 - 2016-08-25AddMedium.1.4 - 2016-06-27Upgrade Facebook API from v2.2 to v2.6.1.3 - 2016-04-07AddIndieAuth.More consistent logging of HTTP requests.Set up Coveralls.1.2 - 2016-01-11Flickr: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-06Flickr: split out flickr_auth.py file.Add a number of utility functions to webutil.1.0 - 2015-06-27Initial PyPi release.DevelopmentPull requests are welcome! Feel free toping me in #indieweb-devwith any questions.First, fork and clone this repo. Then, install theGoogle Cloud SDKand rungcloud components install beta cloud-datastore-emulatorto install thedatastore emulator. 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:gcloudconfigsetprojectoauth-dropins
gitsubmoduleinit
gitsubmoduleupdate
python3-mvenvlocalsourcelocal/bin/activate
pipinstall-rrequirements.txtRun the demo app locally withflask run:gcloudbetaemulatorsdatastorestart--use-firestore-in-datastore-mode--no-store-on-disk--host-port=localhost:8089--quiet</dev/null>&/dev/null&GAE_ENV=localdevFLASK_ENV=developmentflaskrun-p8080To deploy to production:gcloud -q beta app deploy --no-cache oauth-dropins *.yamlThe docs are built withSphinx, includingapidoc,autodoc, andnapoleon. Configuration is indocs/conf.pyTo build them, first install Sphinx withpip install sphinx. (You may want to do this outside your virtualenv; if so, you'll need to reconfigure it to see system packages withpython3 -m venv --system-site-packages local.) Then, rundocs/build.sh.Release instructionsHere's how to package, test, and ship a new release. (Note that this islargely duplicated in granary's readme too.)Run the unit tests.sourcelocal/bin/activate.csh
gcloudbetaemulatorsdatastorestart--use-firestore-in-datastore-mode--no-store-on-disk--host-port=localhost:8089</dev/null>&/dev/null&sleep2sDATASTORE_EMULATOR_HOST=localhost:8081DATASTORE_DATASET=oauth-dropins\python3-munittestdiscoverkill%1
deactivateBump the version number insetup.pyanddocs/conf.py.git grepthe old version number to make sure it only appears in the changelog. Change the current changelog entry inREADME.mdfor this new version fromunreleasedto the current date.Build the docs. If you added any new modules, add them to the appropriate file(s) indocs/source/. Then run./docs/build.sh.git commit -am 'release vX.Y'Upload totest.pypi.orgfor testing.python3setup.pycleanbuildsdist
setenvverX.Ysourcelocal/bin/activate.csh
twineupload-rpypitestdist/oauth-dropins-$ver.tar.gzInstall from test.pypi.org.cd/tmp
python3-mvenvlocalsourcelocal/bin/activate.csh
pip3install--upgradepip# mf2py 1.1.2 on test.pypi.org is broken :(pip3installmf2py
pip3install-ihttps://test.pypi.org/simple--extra-index-urlhttps://pypi.org/simpleoauth-dropins
deactivateSmoke test that the code trivially loads and runs.sourcelocal/bin/activate.csh
python3# run test code belowdeactivateTest code to paste into the interpreter:fromoauth_dropins.webutilimportutilutil.__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.gittag-av$ver--cleanup=verbatim
gitpush
gitpush--tagsClick here to draft a new release on GitHub.EntervX.Yin theTag versionbox. LeaveRelease titleempty. Copy### Notable changesand the changelog contents into the description text box.Upload topypi.org!twineuploaddist/oauth-dropins-$ver.tar.gzRelated workLoginpass/AuthlibAuthomaticPython Social AuthAuthl