Web-Socket Authentication Approaches (Django-Channels)

Reza Torkaman Ahmadi
4 min readJan 18, 2019

Recently I searched on different approaches available to authenticate web-socket communication of django-channels. In this article, I’m going to talk about different solutions available to do this. Maybe somebody has a challenge like this in his projects and find this article useful.

If you are familiar with django-channels, you know that:

Channels is a project that takes Django and extends its abilities beyond HTTP; to handle WebSockets, chat protocols, IoT protocols, and more. It’s built on a Python specification called ASGI.

I’m not gonna get into details of how to add django-channels besides your django project. You can learn how to do it by reading it’s official page or this link.

If you google for how to authenticate django-channels, You may find useful links on how to do it. In this article I do a review on best approaches, But also will introduce a method that I implemented for myself and will describe the differences.

1- Session Authentication

this method is useful when you are creating a full-stack app all with Django. So your client side codes (HTML) are rendered via Django template engine and Django will serve them.

In this scenario, If you authenticate your users, Django will create a session to track authenticated users and will assign a session variable to them. This session value will be sent to client-side via set-cookie header in response. And browser will save it, So all other requests will include this header by default. Even web-socket requests to django-channels.

Django-channels Original documentation, provide a method to authenticate requests that are coming from client-side. This library, by default provide AuthMiddleware class to intercept requests coming to django-channels, fetch session provided in cookie value (which will be sent from client-side) and if the session is valid, will fetch the corresponded user to that session value, If found then update scope attribute in django-channels Object created for that user.

As in docs of django-channels suggested, To use the middleware, wrap it around the appropriate level of consumer in your routing.py:

from django.conf.urls import url

from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack

from myapp import consumers

application = ProtocolTypeRouter({

"websocket": AuthMiddlewareStack(
URLRouter([
url(r"^front(end)/$", consumers.AsyncChatConsumer),
])
),

})

So you can access user object in your web socket methods, like this:

self.user = self.scope["user"]

I don’t get dip in this method, because django-channels doc has a complete guide for this method of authentication. read it here: https://channels.readthedocs.io/en/latest/topics/authentication.html

2- Token Authentication (provide token in HTTP header)

what if you want to authenticate your requests via token? this method is useful when you are sending token in HTTP header.

If the Authorization token is provided in header of HTTP requests, then you can create a custom Authentication Middleware, So that it intercepts requests coming with web-socket to django-channels routers.

This Middleware, will check keys provided in HTTP header. If authorization is sent, will try to fetch it’s value. If the token name is Token then will try to find the corresponded Token Object for this in database. If so will fetch the user for that and update scope['user'] with that value.

Note: This method is used for Basic Token Authentication. If you are using another authorization system in django, change this method. For example If you are using JWT, after you fetched authorization value, Then validate it with authenticate_credentials jwt method. like this:

def authenticate_credentials(self, payload):
User = get_user_model()
username = jwt_get_username_from_payload(payload)

if not username:
msg = _('Invalid payload.')
raise exceptions.AuthenticationFailed(msg)

try:
user = User.objects.get_by_natural_key(username)
except User.DoesNotExist:
msg = _('Invalid signature.')
raise exceptions.AuthenticationFailed(msg)

if not user.is_active:
msg = _('User account is disabled.')
raise exceptions.AuthenticationFailed(msg)

return user

3- Token Authentication (No HTTP header required)

This method that I implemented for one of our projects, Is for situations that you don’t want to send any credentials (like token or cookie) via HTTP header. For example you have separated UI project which is using Rest APIs provided with django and everything is on localStorage.

The concept for this method is that whenever a socket is created between client-side and django-channels, Until the token is not provided in first request of user in socket’s data, and that token is not valid; No other data will be transferred between them.

  • First check if the self.scope['user'].id exists. means that this connection is authenticated.
  • If no user is in scope , Then load data as json, try to fetch token in it. (you can customize it and send token as any json format you want.)
  • Validate this token (based on any token authentication system you are using like Basic, JWT and …). If token is valid, Update this session between client and server and assign then user to self.scope.
  • From now on, each data transferred between client and server via that socket connection, Has a user in it’s scope variable and is authenticated.

Below is the override receive method. If you get the concept that i said, Do as you want with it :)

def receive(self, text_data=None, bytes_data=None):
if self.scope['user'].id:
pass
else:
try:
# It means user is not authenticated yet.
data = json.loads(text_data)
if 'token' in data.keys():
token = data['token']
user = fetch_user_from_token(token)
self.scope['user'] = user

except Exception as e:
# Data is not valid, so close it.
print(e)
pass

if not self.scope['user'].id:
self.close()

I really be glad if anybody who knows any other method, help me to update this article and make it better.

Hope this article was useful. peace :)

--

--

Reza Torkaman Ahmadi

CTO & Co-Founder @ CPOL; A CTF enthusiast & believer in rough consensus and running codes. A person who loves to learn about whatever that makes him excited;)