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.
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
Finally, you’ll need to put the
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
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
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)
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.
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
oobwhen 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
POSTrequests (in violation of section 22.214.171.124.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
oauth_core.pyif you want the server to return the proper errors.
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.
HttpRequest.pathattribute 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
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.
HttpRequest.POSTattributes contain unquoted and decoded (from UTF-8) Unicode strings.
- Despite what its name implies, Django’s
HttpRequest.GETattribute appears to be populated from the parameters passed in the query string. This seems to be true irrespective of whether the request was a
POST. The entity-body of a
GETrequest (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.