My OAuth Solution (Courtesy of Everyone Else)

About 3 days ago (as of writing this post) the OAuth session fixation vulnerability was made public. I'm not going to discuss the history of the vulnerability, the dramatic and gripping story of how Service Providers were notified and gathered together in a secret society that would rival the Illuminati to deal with the issue. I'm not going to discuss it because the movie, graphic novel and bestselling thriller book are all probably in the works.

The attack itself is quite simple, and relies not so much on weaknesses in the protocol itself but on weaknesses in the psychology of Homo Sapiens. What basically happens usually is this (note the following example is very specific to web application consumers, but OAuth flows exist in a few other forms too):

  1. Consumer gets a request token from the Service Provider, and prepares a link to the SP's User Authorization URL with the token and a callback as a query parameter.
  2. User clicks this link and goes to the SP where he/she/it authorizes the consumer to access their account.
  3. The SP then redirects them to the callback which was specified on the User Auth URL.
  4. The Consumer, which has just had its callback GET'd, now attempts to exchange that request token for an access token, which will now allow the Consumer to make authorized requests to the Service Provider on behalf of the User.

The session fixation attack looks like this:

  1. Malicious User (MU) visits the consumer and gets the link to the SP's User Auth URL, but instead of following it, modifies the callback parameter to a blank URL (like Google, or example.com), and tries to get Innocent User (IU) to click on it.
  2. IU grants access to the Consumer, as it appears the entire thing is kosher.
  3. IU is redirected to blank URL, whilst MU now visits the original callback URL. The Consumer will now try to exchange MU's request token for IU's access token, and this will succeed. MU will now have access to IU's account (but only through the Consumer). Because the MU has no idea when IU will authorize the Consumer, MU will probably poll the callback, waiting for the access token to be granted to the Consumer.

So at its heart, the problem comes in two parts:

  1. We need to make sure the MU cannot manipulate the callback URL.
  2. We need to make sure the MU cannot poll the callback URL.
  3. We need to make sure that the user who authorizes the Consumer is the same as the one trying to exchange the request token for the access token.

From hanging around on the appropriate thread on the OAuth mailing list (and contributing to it quite a bit myself), I've pretty much amalgamated a solution based on a few ideas.

  1. To ensure that the callback URL cannot be modified, the entire User Auth URL (callback and all) should be signed by the Consumer. This provides a way for the SP to be certain that the URL was generated by the Consumer (and only the Consumer) and was not modified at any point.
  2. To ensure that MU cannot poll the callback URL, a once-only rule should be mandatory; this means that if the Consumer tries to exchange the request token once and fails (because the user has not (yet) authorized it), any subsequent attempts will fail unconditionally. Thus, if MU does poll the callback URL, the first request will be the only one which even has a chance of working. The next and last point eliminates this, too.
  3. To ensure that the user who authorized the app is the same as the one who tries to exchange the request token, a callback token is required. When the user is redirected from the SP to the Consumer's callback URL, an additional 'oauth_callback_token' query parameter is added with a unique string generated by the SP upon authorization. When the Consumer attempts to exchange the request token for an access token, this parameter must be present in the request or the exchange will fail. Thus, even if MU does poll the callback URL, he/she/it will be unable to get access as he/she/it will not know this token.

A lot of other people on the list are proposing rather massive changes to the OAuth flow, but I like the approach I've laid out here because:

  • It's simple.
  • It works.
  • It requires practically no change in the user flow for web applications.
  • It keeps all the burden of signature/token verification and user authentication on the Service Provider. This is how it should be; the Consumer is a third-party and as such cannot be trusted.

So what do you think?

The Offline Django Documentation Browser

Even though I've been using Django for about a year, I still find I need to look up some things; model fields, the form API or admin configuration stuff. It gets difficult to remember everything with the comprehensive stack that Django provides. So, I set about making it easier for me to use the Django docs, and I will share my experience here.

I began by checking out the Django trunk. In the trunk there's a docs folder which contains all the Django documentation in reStructuredText format. Django has been using the Sphinx documentation system for a while now, so in order to build my own HTML copy of the docs all I needed to do was easy_install Sphinx==0.5.2 (at the moment 0.6 doesn't work with some of Django's Sphinx extensions) and then run make html from within the docs directory. This builds all the documentation as HTML in the docs/_build/html directory.

I then had to somehow make this easily accessible. Since I didn't want to keep pointing my browser at file:///Users/zvoase/Downloads/VCS/django/docs/_build/html/index.html, I figured out a very easy way of serving it from localhost:3378 (don't ask me why I used that port number). If you enter the HTML build directory, and execute python -m SimpleHTTPServer <port_number>, Python will run a web server which just serves up the current directory. I daemonise the process using nohup python -m SimpleHTTPServer 3378 1>/dev/null 2>&1 &, and put this into a shell script which I've set my computer to run on login. So now, I point my browser at http://localhost:3378 and I get the latest Django documentation, which I can even access offline.

This can be made easier still using Fluid, which is a Mac application for building Single-Site Browsers (SSBs). I used it to generate an SSB specifically for http://localhost:3378, and I saved that as /Applications/Django Documentation.app. Now, when I want to see all the Django documentation offline in a very simple chromeless window (because the beauty of an SSB is that you don't need any toolbars), I just type 'Django' into Quicksilver and hit enter. I think that's pretty cool.

Using one form to edit two models

Note: This post is a near-direct translation of an original post by Antonio Melé on the Django.es blog.

Sometimes we need to change information which relates to two or more different models from within only one HTML form. It is quite common, for example, to define a user profile within an app wherein you need to be able to edit information pertaining to both the Django authentication system's User model and your own user profile model simultaneously. We're going to see how this can be done. To follow this example, the django.contrib.auth app needs to be present in the list of installed applications in your project (the INSTALLED_APPS setting).

Suppose we have the following Profile model defined in our models.py:

from django.contrib.auth.models import Userfrom django.db import modelsclass Profile(models.Model):    user = models.OneToOneField(User, related_name='profile')    telephone_number = models.CharField(max_length=16)    address = models.TextField()

We then create a ModelForm subclass for this Profile model and another for django.contrib.auth's built-in User model. Our forms.py file should look like this:

from django import formsfrom django.contrib.auth.models import Userfrom models import Profileclass UserForm(forms.ModelForm):    class Meta(object):        model = User        fields = ('username', 'first_name', 'last_name', 'email')class ProfileForm(forms.ModelForm):    class Meta(object):        model = Profile

As you can see, we only want to edit the fields username, first_name, last_name and email on the User model.

Now we can create the view which will serve the form for editing a user's profile. In order to do this we need to instantiate the two ModelForm subclasses we defined previously. Our views.py should contain the following code:

from django.contrib.auth.decorators import login_requiredfrom django.shortcuts import render_to_responsefrom django.template import RequestContext, loaderfrom forms import ProfileForm, UserForm@login_requireddef edit_profile(request):    if request.method == 'POST':        # The forms submitted by the client.        user_form = UserForm(request.POST, instance=request.user)        profile_form = ProfileForm(request.POST, instance=request.user.profile)        if user_form.is_valid() and profile_form.is_valid():            # The forms validated correctly.            user_form.save()            profile_form.save()            # Change this location as necessary.            return HttpResponseRedirect('/form-saved-successfully/')    else:        # Initialise the forms.        user_form = UserForm(instance=request.user)        profile_form = ProfileForm(instance=request.user.profile)    return render_to_response('edit_profile.html',        {'user_form': user_form, 'profile_form': profile_form},        context_instance=RequestContext(request))

Here we used the @login_required decorator, as clients should be authenticated in order to edit their profile. If the request arrives with the POST method it means they have submitted the forms, in which case we create two Form instances - one for the User model and another for the Profile - with the received data. We validate both forms as we would with only one form, before saving both too. If they are both valid the client is redirected to a 'success' URL. If the forms are invalid, or the request has arrived via the GET HTTP method, we render a template called edit_profile.html to which we pass the two forms. If the forms were submitted but invalid, they will contain the generated errors and data with which they were submitted; otherwise we create two new Form instances and render the template with them. The forms, as they appear in our HTML template, look something like this:

<form action="" method="POST">  {{ user_form }}  {{ profile_form }}  <input type="submit" value="Save changes" /></form>

This template renders a HTML form which contains the specified fields from the User model followed by those from the associated Profile, and which will correctly save any change made in either of the two models.