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

Datamapper – Padrino – warden

I took a break from coding, but was still looking for a useful set of tools for developing web applications. And I think I found a solution that fits my needs (small core but extensible, modular, reasonable features, usable documentation or active user groups at least).

The goal was to create a backend that would output json objects that could be processed in an independent frontend.

First step was to generate a project following the guide

padrino g project -d datamapper -a mysql -e none

I set renderer (-e option) to none because I am using rabl for templating the json output.
For authentication I chose warden. So I added these to the Gemfile

gem 'warden'
gem 'rabl'

Then turned to the app/app.rb and added

use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = myApp
end

Warden::Manager.serialize_into_session do |user|
user.id
end

Warden::Manager.serialize_from_session do |id|
User.get(id)
end

For creating the model I used the padrino generator again, since the user model is pretty straight forward (extend as needed)

padrino g model User username:string password:string email:string

After setting up config/database.rb you can create the database by using

padrino rake dm:create

To have some entries in the database to work with I costumize the db/seeds.rb which is mentioned in the padrino blog tutorial

Having done this warden should be in the system but is not working yet, since we have to define at least one strategy:

For now I like to use a common username/password login, which is already defined as default in manager.default_strategies. (You could add others if you wanted to, look at the warden-wiki for details)

Warden::Strategies.add(:password) do
def valid?
... code goes here ...
end
def authenticate!
... code goes here ...
? success!(user) : fail!("Invalid")
end
end

So in valid? you would define the requirements that have to be met to go on with the authentication process. In this case checking params[“username”] && params[“password”] would make sense.
After creating a usable authentication! function request to a controller can be authenticated via adding env[‘warden’].authenticate! before the login controller code.
If authentication was successful you can add env[‘warden’].authenticated? to following controllers and get the user (or what you decided to return for success) by calling env[‘warden’].user.

I tested this with curl, since the frontend is intended to be independent. I put the login process in a post route, so
after starting padrino

curl -d "username=...&password=..." localhost:3000/login

gave me the defined output of a successful login.

One pitfall when testing a subsequent controller with curl is that in contrast to a browser you have to add the cookie information. In order to get it you could call

curl -vvv -d "username=...&password=..." localhost:3000/login

and can extract the rack.session=… …; and call the controller with

curl --cookie "rack.session=... ...;" localhost:3000/subsequent_controller