Using Django DRF JWT Authentication with Django Channels 🐍
This is for people who are already using django-rest-framework-simplejwt for Django REST Framework user authentication and want to use the same JWT token generated by django-rest-framework-simplejwt
to authenticate users with Channels.
Sending the token over WebSocket from client to server
This step assumes that -
- User is already authenticated to use Django DRF using JWT
- A token is present on client side
We can only make use of querystring to send the token while opening the socket.
Create a new WebSocket using the endpoint -
// Retrieve your token on client side
let token = retrieveToken()
let endpoint = "ws://yourwebsite.com/path"
// Create new WebSocket
let socket = new WebSocket(endpoint + "?token=" + token)
Handling the token and authenticating the user
To authenticate the user in Channels using JWT, we will need to create a custom authentication middleware for Channels.
Make a file yourproject/channelsmiddleware.py
-
from django.db import close_old_connections
from rest_framework_simplejwt.tokens import UntypedToken
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from jwt import decode as jwt_decode
from django.conf import settings
from django.contrib.auth import get_user_model
from urllib.parse import parse_qs
class TokenAuthMiddleware:
"""
Custom token auth middleware
"""
def __init__(self, inner):
# Store the ASGI application we were passed
self.inner = inner
def __call__(self, scope):
# Close old database connections to prevent usage of timed out connections
close_old_connections()
# Get the token
token = parse_qs(scope["query_string"].decode("utf8"))["token"][0]
# Try to authenticate the user
try:
# This will automatically validate the token and raise an error if token is invalid
UntypedToken(token)
except (InvalidToken, TokenError) as e:
# Token is invalid
print(e)
return None
else:
# Then token is valid, decode it
decoded_data = jwt_decode(token, settings.SECRET_KEY, algorithms=["HS256"])
print(decoded_data)
# Will return a dictionary like -
# {
# "token_type": "access",
# "exp": 1568770772,
# "jti": "5c15e80d65b04c20ad34d77b6703251b",
# "user_id": 6
# }
# Get the user using ID
user = get_user_model().objects.get(id=decoded_data["user_id"])
# Return the inner application directly and let it run everything else
return self.inner(dict(scope, user=user))
Now use this middleware in yourproject/routing.py
file -
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import yourapp.routing
from .channelsmiddleware import TokenAuthMiddleware
application = ProtocolTypeRouter(
{
# (http->django views is added by default)
"websocket": TokenAuthMiddleware(
URLRouter(yourapp.routing.websocket_urlpatterns)
)
}
)
Accessing the User in consumer
You can now access the user in yourapp/consumers.py
like -
from channels.generic.websocket import AsyncWebsocketConsumer
class FooConsumer(AsyncWebsocketConsumer):
async def websocket_connect(self, event):
user = self.scope["user"]
await self.accept()