In our last post we gave a detailed description about JSON Web Tokens. In this post we are going to show a sample JSON Web Token Authentication mechanism with the Django Web Framework.
Owasp defines Authentication as the process of verification that an individual, entity or website is who it claims to be. Authentication in the context of web applications is commonly performed by submitting a user name or ID and one or more items of private information that only a given user should know.
HTTP by its nature is stateless, regardless of the backend. Hence most web frameworks come with auth and session management feature. In a typical browser-server case; the browser manages a list of key/value pairs, known as cookies, for each domain. Cookies can be accessed by the server (read) by parsing the Cookie HTTP request header. These cookies can be managed by the server(create, modify, delete) using the Set-Cookie HTTPresponse header.
Web-targeted frameworks provide functions to deal with cookies on a higher level, lets take a look into the django.
Django's Built in Authentication
Django has session and authentication management built-in. It takes care of session and auth via Middlewares, that modify incoming requests and outgoing responses.
Django Session Middleware takes an incoming request looks for a session key in the request’s cookies, and then sets request.session to a SessionStore instance. If the request session is modified, or if the configuration is to save the session every time, it saves the changes and sets a session cookie or deletes if the session has been emptied.
The Auth Middleware sets request.user to a LazyUser. LazyUser here is being used to delay the instantiation of the wrapped class. When request.user is accessed – say by @login_required decorator – LazyUser calls django.contrib.auth.get_user(), passing in the request; get_user() pulls information out of the session and, if the user is authenticated, returns the appropriate User instance. However, if there is no session details for the user, an attempt is made to authenticate the user.
How do we authenticate the user?
Given a dict of credentials(username and password) we call the django.contrib.auth.autenticate(**credentials). This function returns the User object, if successful, or None if credentials were not correct.
Given an authenticated User object, we call the django.contrib.auth.login(request, user). This stores the authentication backend and the user's id into the session. Since, one of the variable in session stands modified, request.session should be updated, this is done by process_response in Session Middleware.
Custom Authentication backend in Django
Django has an easily extensible authentication backend. It allows the ability to change what method checks our user's credentials. An example of this is to create a Social Authentication backend. OAuth providers like Facebook, provide the details of the currently authenticated user. But to maintain the login_required decorator or use the request.user variable we still need to have them logged into django. This is what can be achieved by a custom Auth backend.
Let's implement a basic custom auth backend, where password of all the user's is alphaQ.
Username: alpha Password: alphapass
# import the User object
from django.contrib.auth.models import User
# Define backend class
class BasicCustomBackend(object):
# Create an authentication method
def authenticate(self, username=None, password=None):
try:
# Try to find a user matching the username provided
user = User.objects.get(username=username)
# if successful return user if not return None
if password == 'alphapass':
return user
else:
return None
except User.DoesNotExist:
# No user was found
return None
# Required for the backend to work properly
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
And in the settings.py we add
AUTHENTICATION_BACKENDS = ( 'path.to.Backend', )
This backend is not good for production use, but good enough to demostrate a custom backend. Do refer to the official docs.
JWT and Django Auth
Now that we have understood, the basic authentication process in django, and have written a custom auth backend. Lets move to writing a custom backend based on JSON Web Tokens. To brush up on how JWT Auth works, it will be good to read our article on JWT.
As is evident from the article, there are four steps :
Browsers sends a POST request with username and password to the server.
Server creates a token, and is returned to the browser.
Browser sends Token in Auth Headers.
Server validates the Token, and returns the protected information in the response.
Having a Token Based Authentication requires following steps :
A python library to generate and validate JWTs, we will use python-jose.
A django view that takes username and password and returns a Token.
from jose import jws
from django.http import HttpResponse
import datetime
from django.contrib.auth import authenticate
def create_jwt(request):
"""
the above token need to be saved in database, and a one-to-one
relation should exist with the username/user_pk
"""
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
expiry = datetime.date.today() + timedelta(days=50)
token = jws.sign({'username': user.username, 'expiry':expiry}, 'seKre8', algorithm='HS256')
return HttpResponse(token)
- Custom Auth Backend for the Tokens
class JWTAuthentication(object):
"""
Simple token based authentication.
Clients should authenticate by passing the token key in the "Authorization"
HTTP header, prepended with the string "Token ". For example:
Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
"""
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != b'token':
return None
try:
token = auth[1].decode()
except UnicodeError:
msg = _('Invalid token header. Token string should not contain invalid characters.')
raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(token)
def authenticate_credentials(self, payload):
decoded_dict = jws.verify(payload, 'seKre8', algorithms=['HS256'])
username = decoded_dict.get('username', None)
expiry = decoded_dict.get('expiry', None)
try:
usr = User.objects.get(username=username)
except model.DoesNotExist:
raise exceptions.AuthenticationFailed(_('Invalid token.'))
if not usr.is_active:
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
if expiry < datetime.date.today():
raise exceptions.AuthenticationFailed(_('Token Expired.'))
return (usr, payload)
def authenticate_header(self, request):
return 'Token'
Hope the above article shows how to implement JWT with django. The above steps are just for the purpose of demonstration, and should not be used in production. The following are amongst popular libraries help with Token Based Authentication:
Hope the article was of help. Feel free to submit your thoughts in the comments.
The article originally appeared on Apcelent Tech Blog.
Top comments (1)
Is using request.session also a best practice or safe in django backend?
for example :
request.session['user_id'] = id