Log in

tipfy.ext.auth

This extension provides utilities to support a variety of authentication methods in tipfy using a standard API. Currently supported methods are:

See our examples of multi-auth (source) and gae auth (source).

Overview

To implement authentication, a handler must extend one of the two mixins provided by this extension: AppEngineAuthMixin, to use App Engine's built in auth system, or MultiAuthMixin, for own authentication or one of the supported third party authentication services.

The handler will then have access to the current logged in user as a property, as well as several authentication related methods.

Configuration

These are the configuration options available for this extension. All configuration values are optional.

  • user_model: A db.Model class used for authenticated users, as a string. Define this to provide a custom user model. Default is tipfy.ext.auth.model.User.
  • cookie_name: Name of the autentication cookie. Default is tipfy.auth.
  • session_max_age: Interval in seconds before a user session id is renewed. Default is 1 week.

For own and third party auth, you also need to configure the session extension, which requires a secret key to be defined.

Authentication endpoints

You need to define URL rules used for login, logout and sign up using the following endpoints:

  • auth/login: displays and processes a login form.
  • auth/logout: invalidates the user session.
  • auth/signup: display and process a registration form.

For built in App Engine auth, only auth/signup is required. The others are ignored since these URLs are generated by App Engine. For own and third party auth, all endpoints must be implemented.

Here's an example of defining the three endpoints:

urls.py

from tipfy import Rule

def get_rules(app):
    rules = [
        Rule('/login', endpoint='auth/login', handler='handlers.LoginHandler'),
        Rule('/logout', endpoint='auth/logout', handler='handlers.LogoutHandler'),
        Rule('/register', endpoint='auth/signup', handler='handlers.SignupHandler'),
    ]

    return rules

AppEngineAuthMixin

This mixin is used exclusively for authentication with App Engine's Users API.

Because of its uniqueness, we have no option but to declare that it as a authentication system that doesn't mix with others: if you use App Engine's built in authentication, you don't use any other. On the other hand, it is the simplest to setup and the preferred one to use for single sign-on (SSO) on Google Marketplace apps.

Why have a mixin for App Engine's built-in authentication if the API is simple to use? Here are the main reasons:

  • You can use the decorator @user_required to require a user record stored in datastore after a user signs in.
  • It also adds a convenient access to current logged in user directly inside the handler, as well as the functions to generate auth-related URLs.
  • It standardizes how you create login, logout and signup URLs, and how you check for a logged in user and load an User entity. If you change to a different auth method later, these don't need to be changed in your code.

That said, you are free to ignore this extension and use the API directly if you prefer. We think that it is worth at least as a convenience to a common need: to load users from datastore based on the authentication id from the built in auth system.

Using AppEngineAuthMixin

You simply need to make your handler extend AppEngineAuthMixin to use it:

from tipfy import RequestHandler, Response
from tipfy.ext.auth import AppEngineAuthMixin, user_required

class MyHandler(RequestHandler, AppEngineAuthMixin):
    @user_required
    def get(self, **kwargs):
        return Response('Only logged in users can see this page.')

That's all. Once you extend the mixin, you have access to the following properties or methods from the handler:

  • auth_session: the current user from App Engine's Users API, if any, or None if no user is logged in.
  • auth_current_user: the loaded User entity, if the user is logged in and has a record saved in datastore, or None.
  • auth_is_admin: True if the current user is admin, False otherwise.
  • auth_login_url(redirect=None): generates a login URL using the URL passed as redirect to redirect to after logging in. If redirect is not defined, uses the current URL.
  • auth_logout_url(redirect=None): generates a logout URL using the URL passed as redirect to redirect to after logging in. If redirect is not defined, uses the current URL.
  • auth_signup_url(redirect=None): generates a sign up URL using the URL passed as redirect to redirect to after logging in. If redirect is not defined, uses the current URL.
  • auth_user_model: it is the configured User model, as a property.
  • auth_create_user(username, auth_id, **kwargs): saves a new User entity with the given arguments. You can pass as kwargs any model property. It returns the created entity if it was successful, or None if not (most likely because the username is taken).
  • auth_get_user_entity(username=None, auth_id=None): fetches and returns a User entity using the provided username or auth_id. This is used internally during the auth process but you can override it to provide an alternativce loading method and checkings.

MultiAuthMixin

This mixin is used to provide access to the user logged in using own authentication or third party services. It also provides functions to validate username and password submitted through a form, set the auth session and logout the user.

Using MultiAuthMixin

Your handler must extend MultiAuthMixin and implement sessions to use own or third party authentication. Here's an example:

from tipfy import RequestHandler, Response
from tipfy.ext.auth import MultiAuthMixin, user_required
from tipfy.ext.session import SessionMiddleware, SessionMixin

class MyHandler(RequestHandler, SessionMixin, MultiAuthMixin):
    middleware = [SessionMiddleware]

    @user_required
    def get(self, **kwargs):
        return Response('Only logged in users can see this page.')

That's all. Once you extend the mixin, you have access to the following properties or methods from the handler:

  • auth_session: the current auth session data, a dictionary like object. Logged in users have at least three values defined in the session. Different auth providers may add additional values to the session, such as tokens for api requests. The required values are:
    • id: the authentication id.
    • session_id: a unique session token, also stored with the user record.
    • remember: '1' if the session should persist after the user closes the browser, or '0' if not.
  • auth_current_user: the loaded User entity, if the user is logged in and has a record saved in datastore, or None.
  • auth_is_admin: True if the current user is admin, False otherwise.
  • auth_login_url(redirect=None): generates a login URL using the URL passed as redirect to redirect to after logging in. If redirect is not defined, uses the current URL.
  • auth_logout_url(redirect=None): generates a logout URL using the URL passed as redirect to redirect to after logging in. If redirect is not defined, uses the current URL.
  • auth_signup_url(redirect=None): generates a sign up URL using the URL passed as redirect to redirect to after logging in. If redirect is not defined, uses the current URL.
  • auth_user_model: it is the configured User model, as a property.
  • auth_create_user(username, auth_id, **kwargs): saves a new User entity with the given arguments. You can pass as kwargs any model property. It returns the created entity if it was successful, or None if not (most likely because the username is taken).
  • auth_get_user_entity(username=None, auth_id=None): fetches and returns a User entity using the provided username or auth_id. This is used internally during the auth process but you can override it to provide an alternativce loading method and checkings.
  • auth_login_with_form(username, password, remember=False): Authenticates the current user using data from a form. Returns True if authetication is successful, False otherwise.
  • auth_login_with_third_party(auth_id, remember=False, **kwargs): Called to authenticate the user after a third party confirmed authentication. This always authenticate a user.
  • auth_set_session(auth_id, session_id=None, remember=False, **kwargs): Sets or renews the auth session.
  • auth_logout(): Logs out the current user. This cleans and deletes the authentication session.

The User model

tipfy.ext.auth.model containes a User model that you can use as it is, extend to add additional properties or use as reference to create your own user model. An entity from this model is loaded when a user logs in and is the primary reference for users of your application. The model contains some basic properties that allows multiple authentication systems to be used.

Restricting access using decorators

To restrict access to RequestHandler methods, tipfy.ext.auth provides three decorators:

  • @login_required: the method is executed only if the current user is logged in. Otherwise, the user is redirected to the login URL.
  • @user_required: the method is executed only if the current user is logged in and has a record stored in datastore. If not logged in, the user is redirected to the login URL. If logged in but there's no record stored in datastore, the user is redirected to the signup URL.
  • @admin_required: the method is executed only if the current user is logged in and is an admin (the admin property must be True in the user entity). If not logged in, the user is redirected to the login URL. If logged in but there's no record stored in datastore or the user is not admin, a Forbidden exception is raised.

You must apply one of these decorators to every method that you want to restrict access. For example:

from tipfy import RequestHandler, Response
from tipfy.ext.auth import (AppEngineAuthMixin, login_required, user_required,
    admin_required)


class MyHandler(RequestHandler, AppEngineAuthMixin):
    @login_required
    def get(self, **kwargs):
        return Response('Only logged in users can see this page.')

    @login_required
    def post(self, **kwargs):
        return Response('Only logged in users can post to this page.')


class MyHandler2(RequestHandler, AppEngineAuthMixin):
    @user_required
    def get(self, **kwargs):
        return Response('Only users with a record stored in datastore can see '
            'this page.')


class MyHandler3(RequestHandler, AppEngineAuthMixin):
    @admin_required
    def get(self, **kwargs):
        return Response('Only admins can see this page.')

Restricting access using middleware

If you have several handlers that require a logged in user or admin rights, adding a decorator to every method to restrict access will look repetitive and unnecessary. For these cases (think about an admin panel that requires a logged in user for all requests), using a middleware is a better option.

tipfy.ext.auth provides three middleware classes which are equivalent to the decorators described above, but they apply the restrictions to all methods in a handler. They are:

  • LoginRequiredMiddleware: the handler methods are executed only if the current user is logged in. Otherwise, the user is redirected to the login URL.
  • UserRequiredMiddleware: the handler methods are executed only if the current user is logged in and has a record stored in datastore. If not logged in, the user is redirected to the login URL. If logged in but there's no record stored in datastore, the user is redirected to the signup URL.
  • AdminRequiredMiddleware: the handler methods are executed only if the current user is logged in and is an admin (the admin property must be True in the user entity. If not logged in, the user is redirected to the login URL. If logged in but there's no record stored in datastore or the user is not admin, a Forbidden exception is raised.

They are specially useful in base classes: all extended classes will have the restrictions applied. See an example:

from tipfy import RequestHandler, Response
from tipfy.ext.auth import (AppEngineAuthMixin, UserRequiredMiddleware,
    admin_required)


class BaseAdminHandler(RequestHandler, AppEngineAuthMixin):
    """Base class for all admin panel handlers."""
    middleware = [UserRequiredMiddleware]


class DashboardHandler(BaseAdminHandler):
    def get(self, **kwargs):
        return Response('Only users with a record can see this page.')


class CalendarHandler(BaseAdminHandler):
    def get(self, **kwargs):
        return Response('Only users with a record can see this page.')


class ManageUsersHandler(BaseAdminHandler):
    @admin_required
    def get(self, **kwargs):
        return Response('Only admins can see this page.')

In the example above, we set a base handler class that all others extend. All handlers can only be accessed by logged in users with a record stored in datastore. You can still apply decorators like @admin_required to special methods, as we did in the last class.

Extension Reference


Powered by Moe. Yeah, the name is Moe. Powered by Google App Engine