Web-Socket Authentication Approaches (Django-Channels)
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 fetchtoken
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
toself.scope
. - From now on, each data transferred between client and server via that socket connection, Has a
user
in it’sscope
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 :)