Auth Hive

bzz comes with decorators and classes to support OAuth2 authentication on specific providers that is easy to plug to your tornado server.

It’s worth noting that AuthHive is just an extension to the regular tornado routes. That said, it needs to add some dependencies to the application being run.

To enable authentication, call the AuthHive.configure method passing your tornado app instance. To get all the routes you need to add to your app, just call AuthHive.routes_for:

from tornado.web import Application
from tornado.ioloop import IOLoop
import bzz
import bzz.providers.google as google

providers = [
    google.GoogleProvider,
    # MyCustomProvider
]

app = Application(bzz.flatten([
    # ('/', MyHandler),
    bzz.AuthHive.routes_for(providers)
]))

bzz.AuthHive.configure(app, secret_key='app-secret-key')

Note that flatten() method encapsulates the list of handlers. It is important because the routes_for method returns a list of routes, but Application constructor only support routes, so flatten() does the magic.

The AuthHive class

The bzz framework gives you a AuthHive class to allow easy OAuth2 authentication with a few steps.

class bzz.auth.AuthHive[source]

The AuthHive is the responsible for integrating authentication into your API.

classmethod configure(app, secret_key, expiration=1200, cookie_name='AUTH_TOKEN', authenticated_create=True, authenticated_update=True, authenticated_delete=True, proxy_host=None, proxy_port=None, proxy_username=None, proxy_password=None, authenticated_get=True)[source]

Configure the application to the authentication ecosystem.

Parameters:
  • app (tornado.web.Application instance) – The tornado application to configure
  • secret_key (str) – A string to use for encoding/decoding Jwt that must be private
  • expiration (int) – Time in seconds to the expiration (time to live) of the token
  • cookie_name (str) – The name of the cookie
  • proxy_host (str) – Host of the Proxy
  • proxy_port (str) – Port of the Proxy
  • proxy_username (str) – Username of the Proxy
  • proxy_password (str) – Password of the Proxy
  • authenticated_get (bool) – Should check authentication when listen to bzz.pre-get-instance and bzz.pre-get-list signals. Default is True
  • authenticated_save (bool) – Should check authentication when listen to bzz.pre-create-instance signal. Default is True
  • authenticated_update (bool) – Should check authentication when listen to bzz.pre-update-instance signal. Default is True
  • authenticated_delete (bool) – Should check authentication when listen to bzz.pre-delete-instance signal. Default is True
classmethod routes_for(providers, prefix='')[source]

Returns the list of routes for the authentication ecosystem with the given providers configured.

The routes returned are for these URLs:

  • [prefix]/auth/me/ – To get user data and check if authenticated
  • [prefix]/auth/signin/ – To sign in using the specified provider
  • [prefix]/auth/signout/ – To sign out using the specified provider
Parameters:
  • providers (AuthProvider class or instance) – A list of providers
  • prefix (String) – An optional argument that can be specified as means to include a prefix route (i.e.: ‘/api’);
Returns:

list of tornado routes (url, handler, initializers)

bzz.auth.authenticated(method)[source]

Decorate methods with this to require the user to be authenticated.

If the user is not logged in (cookie token expired, invalid or no token), a 401 unauthorized status code will be returned.

If the user is authenticated, the token cookie will be renewed with more expiration seconds (configured in AuthHive.configure method).

Usage:

import tornado
import bzz

class MyHandler(tornado.web.RequestHandler):

    @bzz.authenticated
    def get(self):
        self.write('I`m authenticated! :)')

Currently Supported Providers

class bzz.providers.google.GoogleProvider(io_loop=None)[source]

Provider to perform authentication with Google OAUTH Apis.

authenticate(*args, **kwargs)[source]

Try to get Google user info and returns it if the given access_token get`s a valid user info in a string json format. If the response was not an status code 200 or get an error on Json, None was returned.

Example of return on success:

{
    id: "1234567890abcdef",
    email: "...@gmail.com",
    name: "Ricardo L. Dani",
    provider: "google"
}

Signing-In

In order to sign-in, the authentication route must be called. Both access_token and provider arguments must be specified. This request must be sent using a POST in JSON format:

POST /auth/signin/ - {'access_token': '1234567890abcdef', 'provider': 'google'}

This method returns a 401 HTTP status code if the access_token or provider is invalid.

On success it set a cookie with the name that was specified when you called AuthHive.configure (or defaults to AUTH_TOKEN) and returns:

200 {authenticated: true}

Signing-Out

Signing-out means clearing the authentication cookie. To do that, a POST to the sign-out route must be sent:

POST /auth/signout/

The response clear the authentication cookie and returns:

200 {loggedOut: true}

Getting User Data

Retrieving information about the authenticated user is as simple as doing a get request to the /me route:

GET /auth/me/

If user is not authenticated, the returned value is a JSON in this format:

200 {authenticated: false}

If authenticated:

200 {authenticated: true, userData: {}}

Authoring a custom provider

Creating a custom provider is as simple as extending bzz.AuthProvider. You must override the authenticate method.

It receives an access_token as argument and should return a dict with whatever should be stored in the JSON Web Token:

{
    id: "1234567890abcdef",
    email: "...@gmail.com",
    name: "Ricardo L. Dani",
    provider: "google"
}

AuthHive Signals

In order to interact with the authenticated user, you can use the authorized_user and unauthorized_user:

signals.authorized_user = None

This signal is triggered when an user authenticates successfully with the API. The arguments for this signal are provider_name and user_data. The provider_name is used as sender and can be used to filter what signals to listen to. The user_data argument is a dict similar to:

{
    id: "1234567890abcdef",
    email: "...@gmail.com",
    name: "Ricardo L. Dani",
    provider: "google"
}

This is the same dict that’s returned in the response to the authenticate route. If you’d like to add more fields to the response, just change this dict to include whatever info you’d like to return:

@bzz.signals.authorized_user.connect
def handle_authorized(self, provider_name, user_data):
    # let's add something to the user_data dict
    user_data['something'] = 'else'

# now the resulting json for the /authenticate method will be similar to:
{
    "id": "1234567890abcdef",
    "email": "...@gmail.com",
    "name": "Ricardo L. Dani",
    "provider": "google",
    "something": "else"
}
signals.unauthorized_user = None

This signal is triggered when an user tries to authenticate with the API but fails. The only argument for this signal is provider_name. It is used as sender and can be used to filter what signals to listen to.