Sign in
Log inSign up

Using Django DRF JWT Authentication with Django Channels 🐍

codegabru's photo
codegabru
·Aug 30, 2019

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()

Python FTW! 🔥