29 April 2015

Posted by: Duncan
Tags: Python | Django

Here is a very simple and I think virtually foolproof Django pattern for authenticating a user and notifying the client to that end.

In a nutshell, this pattern implements a session cookie which can be read by client JavaScript. The cookie is only created if the user is authenticated through Django's session management framework. Attempts to access views when not authenticated cause a redirect to the login page, utterly confounding the casual hacker.

Setup

In your app's settings.py file, add the following;

SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
SESSION_EXPIRE_AT_BROWSER_CLOSE = True

These directives will instruct the session framework to implement a session cookie, which is cleared when the browser window is closed (or if the user logs out).

In the same settings.py file, ensure that Django's session middleware is loaded;

MIDDLEWARE_CLASSES = [
   .
   . 
   'django.contrib.sessions.middleware.SessionMiddleware',
   .
   .
   ]

Views

Starting with a login page, let's assume that a username/password pair are POSTed to an 'authentication view'. This view merely verifies that the username is in a database and then checks whether the passwords match. The password will be encrypted using one of a number of methods, such as secure hashing which, happily, is implemented in the Python standard library via the hashlib package.

Password verification might be something like;

import hashlib
.
.
def _check_password(username, password):

   user_password = _db.fetch(username, 'password')
   if user_password != hashlib.sha224(password).hexdigest():
      return False
      
   return True

If the view successfully authenticates the user, then it can add some data to the Django session dictionary which is a part of each request (assuming that the session framework is loaded);

request.session['username'] = username

That is, the username is only added to the session dictionary if the user has been authenticated. If the user is not authenticated, then the 'username' key will not be in the session dictionary.

It's worth noting that, once a key/value pair are added to the session dictionary, it is propogated to the client through the HTTP response. The client can then look at the browser cookie to check whether the user is logged in.

I also wanted to make sure that some views were unavailable where the user wasn't authenticated. A painless way to do this is by using a decorator;

def logged_in(view_func):
   def _wrapped_view_func(request, *args, **kwargs): 
      authenticated = request.session.get('username', False)
      if not authenticated:
         return redirect('/log-in')            
      else:
         return view_func(request, *args, **kwargs)     
   return _wrapped_view_func
   
@logged_in
def a_view(request):
   # Do some stuff

The decorator simply redirects the HTTP request to the login page if the user isn't authenticated.

JavaScript

It's sometimes nice to show on the client whether the user is logged in or not. There are also some security considerations; should the user be automatically logged out after a period of inactivity? Should it be made clear to the user that he or she is logged in or that he or she needs to log in before doing something? An easy way to implement this is to use the document's cookie object.

function Auth()
{
   this.authenticated = false;
   this.username = null;
   
   var cookie_array = document.cookie.split (";");
   for ( var i=0; i<cookie_array.length; i++ )
   {
      if ( cookie_array[i].indexOf("username") != -1 )
      {
         this.username = cookie_array[i].split("=")[1];
         this.authenticated = true;
         break;
      }
   }
}
var auth = Auth();
if ( auth.authenticated )
{
   // Do some authenticated stuff..
}

And that's it. The power of Django with a little decoration and some trivial JavaScript and we have a pretty robust authentication schema