WSGI middleware that requires Heroku OAuth for all requests.
Python
Switch branches/tags
Nothing to show
Latest commit 08a6609 Oct 26, 2015 @jacobian jacobian Merge pull request #2 from sibson/fix-flask-usage
docfix flask usage

README.md

Heroku Bouncer - Python edition

WSGI middleware that requires Heroku OAuth for all requests.

Inspired and cribbed from heroku-bouncer.

Installation

pip install heroku-bouncer

Usage

  1. Create your OAuth client using /auth/heroku/callback/ as your callback endpoint:

     heroku clients:create likeaboss https://likeaboss.herokuapp.com/auth/heroku/callback/
    
  2. Set SECRET_KEY, HEROKU_OAUTH_ID and HEROKU_OAUTH_SECRET in your environment:

     heroku config:set SECRET_KEY=...
     heroku config:set HEROKU_OAUTH_ID=...
     heroku config:set HEROKU_OAUTH_SECRET=...
    
  3. Wire up the middleware. See options for the options you can pass in here:

     import heroku_bouncer
     from your.wsgi.application import app
    
     app = heroku_bouncer.bouncer(app)
    
  4. This will require Heroku OAuth for all access to the app. The user (i.e. email address of the Heroku account) will be stored in "REMOTE_USER" in the WSGI environ. For more access to the authenticated user, check out the session object (see below).

## The session object

For more details about the user, you can access "wsgioauth2.session" in the WSGI environ. You'll probably be getting this environ from whatever framework you're using. For example, in Django you'll find this in request.META['wsgioauth2.session']; in Flask it'll be flask.request.environ['wsgioauth2.session']

This is a dict-like object with a couple of useful keys:

## Making authenticated requests

Once you've got an authenticated user, you can make OAuth requests on their behalf against the Heroku Platform API. The key is to set two HTTP headers:

  • Set the Authorization header to "Bearer: TOKEN", where TOKEN is the OAuth token found in env["wsgioauth2.session"]["token"].

  • Set the Accept header to application/vnd.heroku+json; version=3 to select the "v3" API.

For details about Heroku API, see the getting started guide and the Platform API reference.

For example, using requests, you could create a new app as the authenticated user using something like this:

headers = {
    'Authorization': 'Bearer: %s' % environ['wsgioauth2.session']['token'],
    'Accept': 'application/vnd.heroku+json; version=3'
}
requests.post('https://api.heroku.com/apps', headers=headers)
## Options

You can pass extra options as keyword arguments to heroku_bouncer.bouncer(). Those options are:

def callback(session):
    return session['user']['email'].endswith('@example.com')

app = bouncer(app, auth_callback=callback)
  • scope - the OAuth scope(s), as defined in Heroku's documentation, that your app requires. If you're requesting more than one scope, the scopes should be separated by spaces (e.g. app = bouncer(app, scope="read write")). Defaults to "identity".

  • path - the path to use for the OAuth callback. This is the same path you'll pass to heroku clients:create; note that it must end in a trailing slash!. Defaults to /auth/heroku/callback/, which you can probably leave alone unless that conflcits with a URL in your real app.

  • cookie - name of the cookie to use. Defaults to "herokuoauthsess".

  • forbidden_path - What path should be used to display the 403 Forbidden page. Any forbidden user will be redirected to this path and a default 403 Forbidden page will be shown. To override the default page see the next option.

  • forbidden_passthrough - by default a generic 403 page will be generated. Set this to True to pass the request through to the protected application.

  • client_id - the OAuth client ID. Read from `os.environ['HEROKU_OAUTH_ID'] if not passed explicitly.

  • client_secret - the OAuth client secret. Read from os.environ['HEROKU_OAUTH_SECRET'] if not passed explicitly.

  • secret_key - a secret key used to sign the session. Read from ``os.environ['SECRET_KEY']` if not passed explicitly.

## Integration with Flask

Hooking up with Flask is pretty simple; you'll just set app.wsgi_app following the example in the documentation:

import flask
import heroku_bouncer

app = flask.Flask(__name__)

#
# ... your app here ...
#

app.wsgi_app = heroku_bouncer.bouncer(app.wsgi_app)
## Integration with Django

Integrating with Django's a bit more complex. First, you'll need to enable authentication against REMOTE_USER. by adding 'django.contrib.auth.middleware.RemoteUserMiddleware' to your MIDDLEWARE_CLASSES - make sure it's after AuthenticationMiddleware.

Then, you'll need to create a remote user backend to map Heroku users to your users. At the very least, you'll need to deal with the fact that Heroku uses emails for usernames and Django doesn't. So a minimal remote user backend might look like this:

import hashlib
from django.contrib.auth.backends import RemoteUserBackend

class HerokuRemoteUserBackend(RemoteUserBackend):
    create_unknown_user = True

    def clean_username(self, username):
        return hashlib.md5(username).hexdigest()

In practice, you may want to do something more complex (probably involving a [custom user object](https://docs.djangoproject.com/en/dev/topics/auth/customizing /#extending-the-existing-user-model)), including probably overriding configure_user() as well to control initial permissions and such. See [the docs for remote user authentication](https://docs.djangoproject.com/en/dev/howto /auth-remote-user/#remoteuserbackend) for more details, as well as the more general documentation on customizing authentication.

Once you've got your remote user backend, you'll need to add it to AUTHENTICATION_BACKENDS:

AUTHENTICATION_BACKENDS = ['myproject.auth.HerokuRemoteUserBackend']

Finally, you'll need to wire it up as WSGI middleware in wsgi.py. Your final wsgi.py should look something like:

import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "abuse.settings")

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

import heroku_bouncer
application = heroku_bouncer.bouncer(application)

Integration with other things

I don't know how to do other things! Please send me a pull request.

Contributing

Work happens on Github. Please send me a pull request!