Check credentials using Django Rest Framework

This post will cover how to authenticate a user’s username and password using a Django Rest Framework endpoint. This functionality allows checking credentials without the need for refreshing the browser.

This is a follow up to my previous post, which covered how to create a User model DRF endpoint (allowing creating, listing, deleting users).

Create the endpoint

note my folder strucure is like so:

apps/
    accounts/
        urls.py
        api/
            views.py
            serializers.py
            authentication.py

In views.py we need to create the AuthView which we will call to check credentials:

from . import authentication, serializers  # see previous post[1] for user serializer.

class AuthView(APIView):
    authentication_classes = (authentication.QuietBasicAuthentication,)
    serializer_class = serializers.UserSerializer

    def post(self, request, *args, **kwargs):
        return Response(self.serializer_class(request.user).data)

[1] https://richardtier.com/2014/02/25/django-rest-framework-user-endpoint/

Then in authenticaiton.py we define our authenticator: We inherit DRF’s BasticAuthentication to check the HTTP_AUTHORIZATION header for correct username and password.

from rest_framework.authentication import BasicAuthentication

class QuietBasicAuthentication(BasicAuthentication):
    # disclaimer: once the user is logged in, this should NOT be used as a
    # substitute for SessionAuthentication, which uses the django session cookie,
    # rather it can check credentials before a session cookie has been granted.
    def authenticate_header(self, request):
        return 'xBasic realm="%s"' % self.www_authenticate_realm

Notice we’re also overriding BasicAuthentication’s authenticate_header method to prevent undesirable behaviour: by default with Basic authentication if user provides wrong credentials the browser prompts the user for their credentials again using a native dialogue box. Rather ugly and bad user experience. To avoid this we ensure the schema returns a custom value other than ‘Basic‘.

We of course need to define the url in urls.py:

from django.conf.urls import patterns, url

from api import views as api_views

urlpatterns = patterns(
    '',
    url(r'^api/auth/$',
        api_views.AuthView.as_view(),
        name='authenticate')
)

Using the endpoint

Now we can do some fun stuff – attempt to authenticate using the endpoint. We need to set the header of the ajax request. We can do this by hooking in with $.ajax’s beforeSend:


function checkCredentials(username, password){
    function setHeader(xhr) {
        // as per HTTP authentication spec [2], credentials must be
        // encoded in base64. Lets use window.btoa [3]
        xhr.setRequestHeader ("Authorization", "Basic " +
                               btoa(username + ':' password));
    }

    $.ajax({type: "POST",  url: "/api/auth/",  beforeSend: setHeader}).
        fail(function(resp){
            console.log('bad credentials.')
        }).
        done(function(resp){
            console.log('welcome ' + resp.email)
        });

[2] https://tools.ietf.org/html/rfc2617
[3] https://developer.mozilla.org/en-US/docs/Web/API/Window.btoa

and to finally use it:


// pass in bad credentials...
checkCredentials('AzureDiamond', 'password');
// ...prints 'bad credentials.'

// pass in good credentials...
checkCredentials('AzureDiamond', 'hunter2');
// ...prints 'welcome AzureDiamond@bash.org'

Security considerations

Only serve this endpoint over HTTPS, in the same way you should serve a conventional login page using HTTPS. We dont wan’t a man in the middle attack getting our auth header!

In a future post we cover how to use this functionality to have persistent login.

Django Rest Framework User Endpoint

DRF is an awesome tool for building web APIs the RESTful way: allowing you to interact with your database like so:

POST request to /api/articles with hdata payload to create new Article instance. PATCH request to /api/articles/1/ to partially update Article with pk of 1..save PUT request to /api/articles/1/ to completely replace Article with pk of 1. DELETE request to /api/articles/1/ to delete Article with pk of 1. GET request to /api/articles/1/ to retrieve Article with pk of 1. HEAD request to /api/articles/1/ to see if Article with pk of exists.

Gone are the days when we POST data like

POST request to /api/create_article with data payload POST request to /api/update_article with data payload POST request to /api/delete_article with data payload

The main advantage I see in RESTful is it gives us sane restrictions we must develop under and in doing so so helps organize our web apps – thereby avoid accidentally being “clever” and implementing a hard to maintain codebase. Since using DRF I found adding new features to my apps quicker, DRYer and with more readable code.

If you are a django user and interested in DRF, do pip install djangorestframework. For existing DRF users note make sure you have DRF >= 2.3.11, which now supports write_only_fields.

Defining the endpoint

Below we go through how to expose the User model to the web using a DRF endpoint to allow creating, updating, listing, deleting User objects. Note my folder structure is:

apps/
    accounts/
        urls.py
        api/
            views.py
            serializers.py
            permissions.py

We need to create a view that will serve list and detail view of users:

from django.contrib.auth.models import User
from rest_framework viewsets
from rest_framework.permissions import AllowAny

from .permissions import IsStaffOrTargetUser


class UserView(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    model = User

    def get_permissions(self):
        # allow non-authenticated user to create via POST
        return (AllowAny() if self.request.method == 'POST'
                else IsStaffOrTargetUser()),

We need to be careful with permissions – we dont want users to be able to view other user objects if they are not staff members.

from rest_framework import permissions


class IsStaffOrTargetUser(permissions.BasePermission):
    def has_permission(self, request, view):
        # allow user to list all users if logged in user is staff
        return view.action == 'retrieve' or request.user.is_staff

    def has_object_permission(self, request, view, obj):
        # allow logged in user to view own details, allows staff to view all records
        return request.user.is_staff or obj == request.user

Next we define the serializer that will serialize Querysets and objects to JSON. We need to be careful on create of User object to handle passwords correctly, and on read not to serialize and return the password to the client.

from django.contrib.auth.models import User
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('password', 'first_name', 'last_name', 'email',)
        write_only_fields = ('password',)
        read_only_fields = ('is_staff', 'is_superuser', 'is_active', 'date_joined',)

    def restore_object(self, attrs, instance=None):
        # call set_password on user object. Without this
        # the password will be stored in plain text.
        user = super(UserSerializer, self).restore_object(attrs, instance)
        user.set_password(attrs['password'])
        return user

Now register the endpoint in the app’s urls.py:

from django.conf.urls import patterns, url, include
from rest_framework import routers

from . import api

router = routers.DefaultRouter()
router.register(r'accounts', api.views.UserView, 'list')

urlpatterns = patterns(
    '',
    url(r'^api/', include(router.urls)),
)

 

Using the endpoint

Below are examples of calling the endpoint using jQuery, showing the request and the data returned.

Create new user

var data = {username: 'new@user.com', password: '****', ...};
$.post('/api/accounts/', data).done(function(data){
    console.log(data);
});
{first_name: "New"
 last_name: "User",
 email: "new@user.com",
 id:4}

Update user details

var data = {email: 'new@user.co.uk'};
$.ajax({url: '/api/accounts/4', type: 'patch', data: data}).done(function(data){
    console.log(data);
});
{first_name: "New"
 last_name: "User",
 email: "new@user.co.uk",
 id:4}

List all users when logged in as staff

$.get('/api/accounts/').done(function(data){
    console.log(data);
});
[{first_name: "Richard"
  last_name: "Tier"
  email: "me@richardtier.com",
  id: 1},
{first_name: "John"
 last_name: "Doe",
 email: "Jon@Doe.com",
 id: 2},
{first_name: "Jane"
 last_name: "Doe",
 email: "jane@doe.com",
 id: 3}];

Retrieve own record when logged in as Jon@Doe.com

$.get('/api/accounts/2').done(function(data){
    console.log(data);
});
{first_name: "John"
 last_name: "Doe",
 email: "john@doe.com",
 id: 2}

Retrieve Jon@Doe.com’s record when NOT logged in as staff member and NOT user Jon@Doe.com

$.get('/api/accounts/1').fail(function(xhr){
    console.log(JSON.parse(xhr.responseText));
});
{detail: "You do not have permission to perform this action."}

In a follow up post we cover checking username and password using DRF.

Selenium in a hurry

I love tools that save me time. Then I start using them. Then using them turns into a chore. A tool I love is a tool I haven’t used enough yet.

I started using Selenium for end to end testing my web apps, and Selenium went from awesome to  chore quite quickly. My gripe was verbosity:

(browser.find_element_by_class_name('contact-menu')
    .find_element_by_class_name('show-all')).click()
browser.find_element_by_id('user-' + str(user_id)).click()
browser.find_element_by_id('write-mail-to-selected').click()
(browser.find_element_by_class_name('user-info-' + str(user_id)
    .find_element_by_class_name('icon-edit')).click()

Writing it took longer than I wanted, and it will take longer than it should to read, and future-me will be negatively affected when maintaining it. Over-verbosity is not my friend – I think a developer is not here to write code: I think I’m here to create features and I prefer doing it with less code where appropriate. Maybe jQuery has fooled me into thinking clicking on a DOM element can be as simple as:

$('#app-footer').click()

Sure, I appreciate the Selenium project attempts to maintain similar syntax across it’s many implementations. Pick up a Java, C, or Python implementation and if you can write in one implementation then you can read it in another. jQuery doesn’t have that issue as its only implemented in javascript. However, I’m not in the business of reading Selenium scripts across multiple languages.

I work with Python in a high pressure agile environment where I’m happy to make my Selenium scripts more Pythonic in order improve my workflow. I dont stand alone in this opinion: the official ElasticSearch library has been implemented across many different languages – and each one focuses on aligning with the language it was implemented on rather than attempting to maintain a similar syntax across several languages. I prefer this approach.

My wrapper makes selenium what I consider more developer-friendly, to give me sugar that allows:

browser.find('.contact-menu').find('.show-all').click()
browser.find('#user-', user_id).click()
browser.find('#write-mail-to-selected').click()
browser.find('#user-info-', user_id).find('.edit').click()

Compared to the vanilla Selenium its 40% less code and I think 40% more awesome. With that defense laid down, below we go through how it was implemented.

from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.firefox.webdriver import WebDriver as Firefox
from selenium.webdriver.firefox.firefox_profile import FirefoxProfile


class CustomFirefoxDriver(Firefox):
    """
    web driver that returns CustomWebElement
    when get_element_by_*, etc are called.

    """

    def __init__(self)
        super(CustomFirefoxDriver, self).__init__()
        self.find = ElementFinder(self)

    def create_web_element(self, element_id):
        return CustomWebElement(self, element_id)


class CustomWebElement(WebElement):
    """Attach ElementFinder instance to web elements."""

    def __init__(self, *args, **kwargs):
        self.find = ElementFinder(self)
        super(CustomWebElement, self).__init__(*args, **kwargs)


class ElementFinder(object):
    """
    non-verbosely find elements. routes to find_element_by_id,
    find_element_by_class_name, etc.

    """

    def __init__(self, element):
        self.element = element

    def __call__(self, many=False, *args, **kwargs):
        """
        if many is True then find_elements_by_*, else find_element_by_*

        note args will be concatenated to create the selector

        """

        selector = ''.join(map(str, args))
        prepend = 'find_element{0}_by_'.format('s' if many else '')
        # rough approximate check if selector is complex css selector, if not then
        # select element by id, class, or tag, and fall back to css_selector
        punctuation = """!"#$%&'()*+,./:;<=>?@[\]^`{|}~"""
        if sum(map(selector.count, punctuation)):
            append = 'css_selector'
        elif selector[0] == '#':
            append = 'id'
            selector = selector[1:]
        elif selector[0] == '.':
            append = 'class_name'
            selector = selector[1:]
        else:
            append = 'tag_name'
            selector = selector

        # now we know the method name to call, lets do it
        return getattr(self.element, prepend + append)(selector)