Custom authentication in Django

After fiddling with Djangos auth-app for a while I decided t rather have my own (I know, why should one do this? Answer: To learn).
It consists of several steps:

  1. registration
  2. activation
  3. adding a password
  4. login

First I created an app for user-management

 $python manage.py startapp user_management    

This gave me the structure to work with.
First I created the usermodel:

 from django.db import models    
 import bcrypt    

 class User(models.Model):

    email = models.CharField(max_length=100, unique=True)
    firstname = models.CharField(max_length=30)
    lastname = models.CharField(max_length=30)
    password = models.CharField(max_length=128)
    last_login = models.DateTimeField(auto_now=True)
    registered_at = models.DateTimeField(auto_now_add=True)
    core_member = models.BooleanField()
    activation_key = models.CharField(max_length=50, null=True)    

The idea here was to have email as username and to have that unique. I don’t consider usernameshis is a good choice for logins but rather a feature for profiles, but that depends on one’s taste I think.

The registration view is pretty straight forward . I create a RegistrationForm object with fields for email, first and last name.
The activation_key is simply a string of randomly chosen ASCII characters and digits.
Activation itself is just creating a link, sending it and comparing the random part of the link and the stored string. If they match is_active is set to True and the user can set his/her password. For passwords I normally store bcrypt hashes in the database (NEVER! store plaintext passwords in a database!). This is quite simple and can be done by following this description.

The function for setting the password goes into the model. For this to work I use a classmethod. As the name suggests, this is a method bound to the class, not an instance of said class which allows to get objects as in “cls.objects.get()” which is the classmethod’s equivalent to self.something in instance methods.

@classmethod
def set_password(cls, user_id, plain_pass):    
    secret = bcrypt.hashpw(plain_pass, bcrypt.gensalt())
    user = cls.objects.get(pk=user_id)
    user.password = secret
    user.save()
    return True

The login process itself is done via another classmethod which I named authenticate:

@classmethod
def authenticate(cls, email, password, request):
    user = cls.objects.get(email__exact=email)
    if bcrypt.hashpw(password, user.password) == user.password:
        request.session['user_id'] = user.id
        user.save() # this is to get last_login updated
        return user
    else:
        return None

(In order for this to work you have to enable the session middleware and the session app in settings.py.)

So, a quick rundown.

Since I use email as an unique identifier for the login the function expects an email address which is used to find the person to authenticate, the plaintext password (e.g. as given from a inputfield) and the request object to make use of a session. (I use database session handling for development but there are alternatives described in the django docs.)

The bcrypt function returns True if given plaintext password hashed and the stored hash match False if not.

After haveing checkd that the user has given the right credentials I’m going to store the user_id in the session which allows me to get the full set of user information should I need it.

I save the user to trigger the auto_now function of the user model in which updates the last_login field to the actual time.

Now with

User.authenticate(email, password, request) 

the user is logged in.

Advertisements

http://popplers5.bandcamp.com/download/track?enc=mp3-128&fsig=127357a657b5b6166a4ba0506e1c9cd0&id=55414696&nl=1&stream=1&ts=1414051107.0?plead=please-dont-download-this-or-our-lawyers-wont-let-us-host-audio
http://bandcamp.com/EmbeddedPlayer/size=medium/bgcol=ffffff/linkcol=0687f5/notracklist=true/transparent=true/album=1901731418/

Lots of code on here was written with the music of this artist 🙂

Listen/purchase: Into The Trees by Zoe Keating

Setting up my own flavour of Django

Okay, so I started doing stuff in python and of course stated playing around with django. And beeing used to padrinorb‘s convenient generators, I had to figure out how to get to my preferred setup. This is what I do:

  1. Run

    django-admin.py startproject projectname

  2. in settings.py
    add

    import os.path
    and add

    os.path.join(os.path.dirname(__file__), ('templates'))
    to TEMPLATE_DIRS

  3. Make dir templates/ in the project folder
  4. Make dir views/ in the project folder
  5. Add an __init__.py file
  6. import your views in __init__ (e.g.

    from index import hello
    if you have a view file called index.py containing a function hello())

  7. In templates I put subdirs for all sites and a base.html which holds the frame for all sites.
  8. Now in urls.py import all views via

    from views import *

So, this gives me a view and a template dir as well as a frame for the sites.

Now that I got the views and template going I would like to have a seperate dir for static contents. Django’s static dir is simply /static whih is fine by me, but making a directory named static and putting stuff in won’t do. You have to put


STATICFILES_DIRS = (os.path.join(os.path.dirname(__file__), 'static/'),)

After putting


{% load staticfiles %}

into the base.html. You can insert static files like css, image and so on by putting


{% static foo/bar.ext %}

into the template tag.