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.
Can’t wait to hear about the next post. I’m following this tutorial to make an app. It is great. There aren’t many examples about all this. Thank you.
Thanks Nicolas! Happy you found it helpful. Next post is up now.