Authentication in Django

Ok, as I told yesterday, I'm working on a forum, to get to know Django a bit better (when darcs from DarwinPorts actually builds on my Mac OS X.4, I'll make a repository available). Today, I set myself two goals:

  1. Be able to start new threads and post to ongoing threads
  2. Authenticate users and only allow authenticated user to do the previous.

Of course I could much more, but I want to take it slow (so I can play WoW again with my girlfriend tonight :P). In this post I try to explain how I'm building it.

First of all, I'll be using the built-in authentication mechanism from Django. That's just convenient. I check in the view (forum/views/forum.py) if the user is authenticated or not. If not, I present the user with a login functionality. If so, the user can post a message to the thread.

At the present, the view is defined like so:

from django.models.forum import forums, threads

from django.core.extensions import render_to_response, get_object_or_404



[... other methods ...]



def show(request, thread_id):
        t = get_object_or_404(threads, pk=thread_id)
        return render_to_response('forum/forums_thread', {'thread': t})

The template is a simple thing too, like so:

<h1>{{ thread.subject }}</h1>



{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}



<table>

{% for post in thread.get_post_list %}

<tr><td>Author:</td><td>{{ post.get_user.username }}</td>
    <td>Date posted:</td><td>{{ post.date }}</td></tr>
<tr><td colspan="4">{{ post.content }}</td></tr>

{% endfor %}

</table>

To make sure the view has access to the methods defined in Django for authentication, I import users. And I also need to make sure I have access to the user in the template. First, I change the imports into:

from django.models.forum import forums, threads

from django.core.extensions import render_to_response, get_object_or_404, DjangoContext

from django.models.auth import users

DjangoContext is an easy way to transmit user info to the template. I need to change a little code, just one line:

def show(request, thread_id):
        t = get_object_or_404(threads, pk=thread_id)
        return render_to_response('forum/forums_thread', {
                'thread': t,
         }, context_instance=DjangoContext(request))

Now I have access to the user information that's stored in the cookie, if the user is logged in. Yes, Django stores it's login information in a cookie, via a session. But more on that later. First, we need to change the template, so it displays a login form instead of the username, if the user isn't logged in. This is quite simpl, really. Look at this:

<p>Post a new message here:</p>



{% if user.is_anonymous %}

<form action="/mylogin/" method="post">

<input type="hidden" name="redirect" value="/post/{{ thread.id }}">

<table><tr><td>Username:</td><td><input type="text" name="name"></td></tr>

<tr><td>Password:</td><td><input type="password" name="password"></td></tr>

<tr><td colspan="2"><textarea name="content" cols="10" rows="5"></textarea></td></tr>

</table>

</form>

{% else %}

<form action="/post/{{ thread.id }}" method="post">

<table><tr><td>Username:</td><td>{{ user.username }}</td></tr>

<tr><td colspan="2"><textarea name="content" cols="50" rows="10"></textarea></td></tr>

</table>

</form>

{% endif %}

Quite simple, huh? I thought so, too. As you can see, the form for the anonymous user is POSTed to /mylogin/ and the form for the authenticated user is POSTed to /post/thread_id/. But I tell the script at /mylogin/ that it needs to redirect to /post/thread_id/ with a hidden input field.

As I said before, authentication is stored in a session. Although I would have prefered http authentication, this isn't really a bad thing. Maybe I'll hack http authentication in it, once I'm better versed at Python ;-) For now, what we need to do at /mylogin/ is simple, check if the information entered is valid and set the cookie, if it is valid.

For that, we make a new view. I've called it simply login. It looks like this, but it's far from finished.

def login(request):
        username = request.POST['name']
        password = request.POST['password']
        user_cache = users.get_object(username__exact=username)
        if user_cache is not None and not user_cache.check_password(password):
                user_cache = None
        request.session[users.SESSION_KEY] = user_cache.id
        return HttpResponseRedirect(request.POST['redirect'])

As I said, it's far from finished, because it doesn't do any error-handling yet. I'll built that later, when I'm done sleeping or something ;-) This simply creates a User object, based on the username that was entered, checks the password that was entered, and if that wasn't correct, destroy the object. If it was correct, we set the session cookie with the session id. All done. We redirect to the URL that was given in the hidden form element.

There are a few problems still with the code above, but this will get you along, I hope. Error-handling is one thing that needs to be added. But cookie-testing needs to be done, too. Another problem is that the POSTed data isn't forwarded with the HttpResponseRedirect (it isn't supposed to be, if you want to follow rfc's), so we need to store the "content" textarea in the session and read it in the posting code. Take a look at the source references below for a better idea on how to write proper verifications. I might fix it all tomorrow and post it here :)

How I got so smart

Well, actually, I'm not. I had a lot of help from #django on irc.freenode.org and especially the following documents:

Comments

Comments powered by Disqus