Advanced Authentication in TurboGears 2 – Part 1

TurboGears is one of the best python web frameworks you can find this days. I could start listing its features but this post is already long enough and you can read about them in the official TurboGears website. Also, if you are interested in what the title of this post says it is about, you may already know one or two things about TurboGears. So let’s get to the point: Authentication.

Authentication is the act of verifying that somebody is really who he/she claims to be, is about finding who you are. Authorization, on the other hand, is the act of granting access to given resources depending on who would use them. For example, allowing registered members to leave comments on a blog, or allowing your friends to see your pictures while others cannot. In other words, finding what you may do (Authentication and Authorization in TurboGears 2).

TurboGears 2 uses two frameworks to deal with authentication and authorization. Together, these frameworks, are part of a robust, extendable and pluggable system that works in almost any situation but can be extended to suit your needs if it doesn’t. The two frameworks are repoze.who and repoze.what:

  • repoze.who, a framework for authentication in WSGI applications. You normally don’t have to care about it because by default TG2 applications ship all the code to set it up (as long as you had selected such an option when you created the project), but if you need something more advanced you are at the right place.
  • repoze.what, the successor of tg.ext.repoze.who and tgext.authorization (used in unstable TG2 releases), is a framework for authorization that is mostly compatible with the TurboGears 1.x Identity authentication, identification and authorization system.

Normal authentication, using username and password, can be easily enabled in existing TurboGears applications and is even easier to get if you’re creating a new project. However, if you need support for other authentication methods like Facebook Connect, Sign in with Twitter or any other OAuth based authentication method, you’ll be expending a few hours of your time playing with the authentication and authorization system.

This post is about how to create a TurboGears 2 project with support for standard username and password login, Facebook Connect and Sign in with Twitter, all at the same time. There will be a lot of code to show and thus the post will be long so I have split it in three parts:

  • Part 1: Using a .INI file to configure authentication and authorization middleware in TurboGears. In Part 1 we’re going to start with a new TurboGears 2 project without authentication and authorization support and then we’re going to configure repoze.who and repoze.what by hand. Adding a middleware and using a .INI file to define its behavior, among other things. At the end of the post your application will support normal username/password authentication.
  • Part 2: Adding support for Facebook Connect. Part 2 is about adding a second authentication method, Facebook Connect. We’ll create a repoze.who and repoze.what plugins, use Facebook Python SDK to gather user information from facebook servers and use Facebook JavaScript SDK in the client side to handle cookie creation for the authentication method to work.
  • Part 3: Adding support for Sign in with Twitter. Part 3 is a lot like Part 2, but instead of Facebook Connect we’re going to add Sign in with Twitter as our third authentication method. Again, we’ll create a repoze.who plugin and we’re going to use Tweepy library to get information from user’s twitter account.

Before we start, there are a few things you may need to know:

  • I assume you already have TurboGears 2.1 installed.
  • You need access to a Facebook Application. In Part 2 you’ll need the Application ID, API Key and Application Secret.
  • You need access to a Twitter Application. In Part 3 you’ll need the Consumer Key and Consumer Secret. Also you probably will need to change the Callback URL.
  • You need Apache installed on your machine.

Update (09/28/2010)

The following 8 steps lead to a project with several errors regarding broken links or missing controller actions. To fix those problems the code in Step 8. was revised and updated, Step 9. was introduced and the following must be done:

  1. Open project/templates/master.html and replace logout_handler with logout.
  2. Open project/templates/login.html and replace login_handler with authenticate.

Part 1. Using a .INI file to configure authentication and authorization middleware in TurboGears

Step 1. Create a TurboGears project

TurboGears provides a suite of tools for working with projects by adding several commands to the Python command line tool paster. In this tutorial we are going to use quickstart, setup and serve. The first tool you’ll need is quickstart, which initializes a TurboGears project. Go to a command line and run the following command:

[codesyntax lang=”bash”]

[/codesyntax]

For simplicity, the name of the package will be just project. I’ll use Mako templates, but there is no problem if you want to use Genshi; we won’t be working much on the templates. And, finally, answer no to the question Do you need authentication and authorization in this project?, that’s just what we are going to set up manually in what is left of this post.

To test your new project, cd into your tg-advanced-authentication directory, go to a command line and run the following command:

[codesyntax lang=”bash”]

[/codesyntax]

Open your favorite browser and go to http://localhost:8080/. You should see a page with a big title “Welcome to TurboGears 2”.

Step 2. Create a VirtualHost to use as a proxy for the application

We need a VirtualHost because Facebook needs the application to be running on a well formed domain and localhost:8080 won’t work. Facebook Connect will only work if you access the application on the domain specified in the Facebook Application settings.

Use the following VirtualHost template to create your Apache configuration file:

[codesyntax lang=”apache”]

[/codesyntax]

The document root should point to directory containing a directory called logs and a symlink to the public directory within your project (project/project/public). Make sure Apache can read those locations. Also, I assume you’re testing locally so you may want to add an alias in /etc/hosts to example.com (or the domain you used in the VirtulHost configuration file).

Start or reload Apache and serve (paster serve --reload development.ini) the application. Go to your favorite browser and open http://example.com/, you should see the page with the big title again.

Step 3. Add custom middleware for authentication and authorization support

When TurboGears receives a request, the request is passed through a series of middleware that take care of the process of creating a response and finally a page or a resource that you can access in the web browser. Two of those middleware are for authentication and authorization and we are about to configure them:

Let’s start by opening project/config/middleware.py and change its content to look exactly like the code below:

[codesyntax lang=”python”]

[/codesyntax]

What we just did was to add a custom middleware for authentication and authorization that we can configure using a .INI file.

To make things easier during development we need to define a logger for repoze.who and repoze.what. That way we know what’s going on while the request is being processed. That said, add the following code to your development.ini , just put it near the other logger definitions:

[codesyntax lang=”ini”]

[/codesyntax]

Step 4. Create SQLAlchemy models for storing users, groups and permissions.

If we need authentication and authorization, that means we have users and there are different kind of users and every kind of users will be allowed or forbidden to do something in the application. We need to store all that information somehow, here we’re going to use a database and three models: User, Group (kind of user) and Permission (what an user of a certain kind is allowed to do).

TurboGears already created an auth module for you, but it’s empty. To fix that, replace the content of the module project.model.auth with following code:

[codesyntax lang=”python”]

[/codesyntax]

Those models are a modified version of models you would get using paster quickstart -a project. Some of the changes I’ve made are listed below:

  • I changed the name of some columns: group_name was renamed to slug; permission_name and display_name were renamed to name, group_id, permission_id and user_id were renamed to id and email_address was reneamed to email.
  • The name of the tables were changed to Groups, Users, Permissions, UserGroups and GroupPermissions.
  • The User model doesn’t have an user_name column. I decided to remove the user_name column and use the email as username for normal login. If the user is using Facebook Connect or Sign in wth Twitter then it will be authenticated using the respective OAuth tokens.
  • The method by_user_name was removed from User class and __repr__ and __unicode__ methods were updated according to the other changes.

Don’t forget to import User, Group and Permission in your project/model/__init__.py file:

[codesyntax lang=”python”]

[/codesyntax]

Step 5. Create the .INI file

Back in Step 3, when we added a custom middleware for authentication and authorization we set the configuration file as who.ini. In that file we’re going to describe the plugins we’ll use for identification and authorization, to remember credentials and provide additional user information (the metadata). For Part 1 of this post the .INI file we need is shown below:

[codesyntax lang=”ini”]

[/codesyntax]

Let’s talk about what this file does. First, it defines three plugins:

  • form, an instance of repoze.who.plugins.friendlyform.FriendlyFormPlugin responsable for showing the login form and collecting user credentials (email and password) to be used later by other plugins to complete the authentication process.
  • ticket, an instance of repoze.who.plugins.auth_tkt.AuthTktCookiePlugin. It’s the plugin that creates the cookies to keep the user logged in and delete them after he/she logs out.
  • sqlauth, an instance of repoze.who.plugins.sa.SQLAlchemyAuthenticatorPlugin. It’s responsable for taking the credentials collected by form and make sure an user with that email address and that password exists in the database. The plugin is defined in .INI file but configured in project.lib.auth, a module we’ll create in the next step.

The last part of the file makes sure every plugin is assigned to right part of the process of authentication. form and ticket are Identifiers, sqlauth is an Authenticator, form is also a Challenger and, user and group, two plugins we’ll define later, are the Metadata Providers. Read more more about type of plugins used by repoze.who.

Step 6. Create project.lib.auth module

In previous step we referenced a project.lib.auth module that still doesn’t exist. That module is a complement to the authentication and authorization middleware we’re configuring. There, we’ll create instances for the Authenticator plugin form and the Metadata Providers user and group. Also, in Part 2 and Part 3 of this post, we’ll create new Identifier plugins to support Facebook Connect and Sign in with Twitter.

At this point, the module should look something like the code below:

[codesyntax lang=”python”]

[/codesyntax]

Wondering what those *.translations['foo'] = 'bar' means? Remember those column names I changed when we where defining the models? Turns out the default names are the names all these plugins expect to find and since we changed them we need to provide translations so the plugins can still work.

At this point, we’ve already finished defining the authentication and authorization middleware. But we still need to do a few things before we can test our changes.

Step 7. Create default Users, Groups and Permissions

We already defined models for storing users, groups and permissions, but if we want to test authentication, we need to add some data to those models. TurboGears allows you to create instances of your models and insert them right when the database is being created. All you need to do is create those instances in bootstrap function of project.websetup.bootstrap module.

Add the following code to body of the bootstrap function:

[codesyntax lang=”python”]

[/codesyntax]

Then stop your application, run paster setup-app development.ini and start you application again. Now there is two users, one group and one permission. We’re almost ready to test.

Step 8. Create necessary controller methods

Some of the plugins defined in the .INI file need additional help to fullfil their purpose, form is one of them. It needs some actions to be defined, the actions we specified in login_form_url, post_login_url and post_logout_url.

We also may want to define other actions so we can fully test the authorization capabilities of TurboGears. /auth simply let you see a page explaining authentication and authorization, /mange_permission_only can only be seen by users with the manage permission and editor_user_only can only be seen by the user with email address editor@somedomain.com. All that is possible using something called predicates.

Add the following imports at the top of project.controllers.root module:

[codesyntax lang=”python”]

[/codesyntax]

Then, add the following code to the body of the RootController of your application:

[codesyntax lang=”python”]

[/codesyntax]

Finally, add the following code to your project.lib.base module, just before the return call in the __call__ method of BaseController. This is necessary so the logged in user’s information, if any, is available to the templates and the application:

[codesyntax lang=”python”]

[/codesyntax]

Then you’ll need to insert from tg import request at the top of that module.

Step 9. Create SecureController

Open project.controllers.secure module and change its content to the following code:

[codesyntax lang=”python”]

[/codesyntax]

And that’s the last file we’ll need to edit for now. Start or restart the application if you haven’t done that already and open http://example.com/ in your browser. If everything is fine you should see the “Welcome to TurboGears 2” page, but this time, a “Login” link should appear in the navigation section of that page.

Now, start playing around, try to login using manager@somedomain.com as username and managepass as password. Then go to http://example.com/auth and try to follow the instructions, but remember, we’re using email addresses as username.

Conclusion

We have done, with a lot of effort, what we could have just achieved running paster quickstart -a project. However, what we are going to do in Part 2 and Part 3 of this post wouldn’t be possible without going through the process we just completed.

Come back next week to see how to add support for Facebook Connect to this TurboGears application. Thanks for reading.

Update: Nov 19/2011

There is a problem when trying to access the admin/permissions/ and admin/groups/ pages. To fix it you need to add more translations when defining the AdminController (http://goo.gl/bh4lL). Thank you to psilar for letting me know about this bug.

  • Woh, this is awesome. I really want to read part 2 and 3 (so consider this encouragement to write them!)

  • Pingback: Facebook and twitter login options | NPSX blog()

  • psilar

    Great tutorial but I’m having trouble getting it to work.  After following all the steps and then attempting to login, I get the error:

    AttributeError: type object ‘User’ has no attribute ‘user_name’

    The translations are in place but don’t seem to be picked up for some reason.  I’ve tried looking through the repoze documentation but can’t figure out if the usage has changed somehow (seems the same).  Any idea how to resolve this issue?

    • Anonymous

      Hello @904da5bbe445098ecbec1e5c39980bce:disqus, the errors seems to be related with the translations. Since you said the translations are in place I’m out of options here.

      There is a GitHub repository with code from this tutorial https://github.com/wvega/tg-advanced-authentication/tags. If you download tag step-9, are you able to run that example? Is the code similiar to yours? Please note that the translation in question is introduced in step-6.

      • psilar

        Ok, so I unpacked step 13 and I can login but when I try to look at the /admin/permissions/ page I get
        AttributeError: ‘Permission’ object has no attribute ‘permission_id’
         and I can login but when I try to look at the /admin/permissions/ page I get
        and a similar error for /admin/groups/.  Any idea why this is?  I’ve spotted a translations table in tgext/admin/tgadminconfig.py but am unsure how to update it.  Tried adding a dictionary to the models but that didn’t help.

        • Anonymous

          Hi psilar, I can confirm your problem, it’s bug in the code provided with this post.

          We need to add more translations in order to make this work. The AdminController takes an optional parameter translations where you can define custom names for group_id, group_name, permission_id, permission_name and permission_description.

          I have committed changes to the repo I mentioned earlier fixing the problem: http://goo.gl/bh4lL. After changing those lines I can successfully access permissions, users and groups pages.

          Let me know if that helps you too. Thank you for posting.