Turbo OAuth for Django

Presented for your consideration: an implementation of an OAuth server for Django. Aside from one application-specific piece it is production-ready, and you’re free to use it (and/or modify it) without restriction. I think it’s also worth a look if you’re just curious about how OAuth works; the core OAuth code is only about 250 lines, and completely self-contained (i.e., this project has no dependencies on other OAuth packages). You can download the app here, and the supporting templates (for 400 and 401 errors) here.

Deployment

The turbo_oauth app is designed to be placed in the sharedapps directory of my standard project layout; if you use another layout you may need to fiddle with some of the import statements. You’ll also need to add this line to your main urls.py file to place the OAuth URLs under the /oauth/ path:

(r'^oauth/', include('sharedapps.turbo_oauth.urls'))

Finally, you’ll need to put the 400.html and 401.html templates (supplied above) in some directory where they can be found by Django’s template system; they’re used when turbo_oauth encounters certain error conditions. (If you’ve got your own templates with these names, you may need to do some work to resolve the conflict. Look to the Http400 and Http401 classes in oauth_core.py. Sorry ’bout that.)

Remember to set up the DB tables for the new app by running syncdb in your projects’s main directory:

python manage.py syncdb

Usage

It’s pretty simple to integrate OAuth with a view: just use the @oauth_user_required decorator from oauth_core.py, like this:

@oauth_user_required
def some_view(request):
	user = request.oauth_data.user
	return HttpResponse('OAuth-verified access of user "%s\'s" data.'%user.username)

The request.oauth_data.user object is just a Django User, so it’s fully integrated with Django’s authentication system.

Turbo

There are some minor caveats and qualifications associated with the turbo_oauth system, but the big one is hinted at right in the name, which contains that ominous word: “turbo”.

The stickiest and most application-specific part of any OAuth implementation is addressing section 2.2 of the spec: “Resource Owner Authorization”. This is the part of your application that lets a user log in and approve a client app’s request to access his data. My implementation doesn’t address this at all, side-stepping the problem with the inelegantly named process of “turborization”.

“Turborizing” is like authorizing, except that instead of being granted access to an existing user’s data, the turborized token is instead granted access to the data of a newly created user. This may strike you as odd, but it made sense for my application. It also has the virtue of letting me provide a complete OAuth implementation without addressing issues that will probably vary quite a bit from project to project (e.g., “what should the authorization UI look like?”).

The bad news is that if you want to implement a more conventional OAuth API, you’ll have to build your own views to implement section 2.2. The good news is that you’d probably have to do that in any case, and the existing turbo_oauth code will, hopefully, make the process as easy as possible.

Caveats

As for the aforementioned minor caveats and qualifications:

  • The server requires that OAuth parameters be transmitted in the HTTP Authorization header.
  • The server requires the HMAC-SHA1 signature method.
  • The server requires an oauth_callback parameter of oob when temporary credentials are requested. (This is the one you’ll most likely have to change.)
  • The server does not require the use of SSL at any point (in violation of sections 2.1 and 2.3).
  • The server only signs parameters from the entity-body for POST requests (in violation of section 3.4.1.3.1, which indicates that they should be signed for any request that meets certain requirements).
  • The server returns 400 errors in many cases in which it “should” return 401 errors. This reflects the particular needs of the application for which it was built. Modify the _Http401_soft class in oauth_core.py if you want the server to return the proper errors.

Fun Facts

I spent a fair bit of time trying to ensure that the server worked properly with Unicode parameters. In the course of that work I learned some non-intuitive things about the way that Django and Python extract data from HTTP request objects. These are summarized below.

  • Django’s HttpRequest.path attribute returns an unquoted and decoded (from UTF-8) Unicode string; if %C2%A9%202011%2C%20Foo%2C%20Inc. is sent over the wire, © 2011, Foo, Inc. will be returned by the path attribute.
  • Python’s urlparse.parse_qs() and urlparse.parse_qsl() methods unquote their return values, but do not decode them; if you want Unicode, you’ll have to invoke decode('utf-8') on the strings inside the return values of these functions yourself.
  • Django’s HttpRequest.GET and HttpRequest.POST attributes contain unquoted and decoded (from UTF-8) Unicode strings.
  • Despite what its name implies, Django’s HttpRequest.GET attribute appears to be populated from the parameters passed in the query string. This seems to be true irrespective of whether the request was a GET or a POST. The entity-body of a GET request (if present) seems to be ignored, and does not affect the contents of HttpRequest.GET. Since this behavior is a little weird and not that well documented, I didn’t rely on it in my code.
Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in Python. Bookmark the permalink.

Comments are closed.