Why is my WebSocket connection being rejected with "Unauthenticated user" in Django Channels even with a valid JWT token?
I am working on a real-time chat application using Django Channels and WebSockets. I have implemented a custom user authentication system using JWT tokens and attached the token-based authentication to the WebSocket connection using Django Channels middleware. However, my WebSocket connection is always being rejected with the message: "Unauthenticated user attempted to connect."
, despite sending a valid JWT token in the Authorization
header.
Here is the relevant code and setup for my project:
Project Setup:
- Django Version: 4.1.4
- Django Channels Version: 4.0.0
- ASGI Server: Daphne
- Custom User Model:
BasicUserProfile
(which stores the JWT token in theauth_token
field)
Code:
1. CustomAuthMiddleware
- Middleware for JWT Authentication:
import jwt
from datetime import datetime
from channels.middleware.base import BaseMiddleware
from authentication.models import BasicUserProfile
from django.contrib.auth.models import AnonymousUser
from django.conf import settings
from channels.db import database_sync_to_async
class CustomAuthMiddleware(BaseMiddleware):
async def populate_scope(self, scope):
user = scope.get('user', None)
if user is None:
token = self.get_token_from_headers(scope)
if token:
user = await self.get_user_by_token(token)
else:
user = AnonymousUser()
scope['user'] = user
def get_token_from_headers(self, scope):
headers = dict(scope.get('headers', []))
token = headers.get(b'authorization', None)
if token:
token_str = token.decode()
if token_str.startswith("Bearer "):
return token_str[len("Bearer "):]
return None
@database_sync_to_async
def get_user_by_token(self, token):
try:
decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
if decoded_token.get('exp') and decoded_token['exp'] < datetime.utcnow().timestamp():
return AnonymousUser()
user_profile = BasicUserProfile.objects.get(auth_token=token)
return user_profile.user
except (jwt.ExpiredSignatureError, jwt.DecodeError, BasicUserProfile.DoesNotExist):
return AnonymousUser()
2. ChatConsumer
- WebSocket Consumer:
import json
import logging
from channels.generic.websocket import AsyncWebsocketConsumer
from authentication.models import BasicUserProfile
from .models import Chat
from .serializers import ChatSerializer
from django.contrib.auth.models import AnonymousUser
logger = logging.getLogger(__name__)
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
current_user = self.scope.get('user', None)
if current_user is None or not current_user.is_authenticated:
logger.warning("Unauthenticated user attempted to connect.")
await self.close(code=4000)
return
receiver_username = self.scope['url_route']['kwargs']['username']
try:
current_profile = await self.get_user_profile(current_user.id)
receiver_profile = await self.get_receiver_profile(receiver_username)
except BasicUserProfile.DoesNotExist:
logger.error(f"Profile not found for user {current_user.id} or receiver {receiver_username}")
await self.close(code=4001)
return
self.room_name = f'{min(current_profile.id, receiver_profile.id)}_{max(current_profile.id, receiver_profile.id)}'
self.room_group_name = f'chat_{self.room_name}'
self.sender_profile = current_profile
self.receiver_profile = receiver_profile
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
if hasattr(self, 'room_group_name'):
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
logger.debug(f"User {self.scope['user'].username} disconnected from {self.room_group_name}")
async def receive(self, text_data):
data = json.loads(text_data)
message = data.get('message', '')
sender_username = data.get('senderUsername', '')
sender_profile = await self.get_user_profile_by_username(sender_username)
if not sender_profile:
logger.error(f"Sender profile for {sender_username} not found.")
return
chat_message = await self.save_message(sender_profile, message)
chat_history = await self.get_chat_history()
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'senderUsername': sender_username,
'chat_history': chat_history,
}
)
async def chat_message(self, event):
message = event['message']
sender_username = event['senderUsername']
chat_history = event['chat_history']
await self.send(text_data=json.dumps({
'message': message,
'senderUsername': sender_username,
'chat_history': chat_history,
}))
@database_sync_to_async
def get_user_profile(self, user_id):
return BasicUserProfile.objects.get(user__id=user_id)
@database_sync_to_async
def get_receiver_profile(self, username):
return BasicUserProfile.objects.get(user__username=username)
@database_sync_to_async
def get_user_profile_by_username(self, username):
return BasicUserProfile.objects.get(user__username=username)
@database_sync_to_async
def save_message(self, sender, message):
chat = Chat.objects.create(sender=sender, content=message, receiver=self.receiver_profile)
return chat
@database_sync_to_async
def get_chat_history(self):
chat_history_feed = Chat.objects.filter(sender=self.sender_profile, receiver=self.receiver_profile) | \
Chat.objects.filter(sender=self.receiver_profile, receiver=self.sender_profile)
chat_serializer = ChatSerializer(chat_history_feed, many=True)
return chat_serializer.data
Problem:
Despite implementing JWT-based authentication via middleware, the WebSocket connection is always rejected with the message:
"Unauthenticated user attempted to connect."
.
I am sending the JWT token as a Bearer
token in the Authorization
header of the WebSocket request, but the connection is not being authenticated successfully.
What I've Tried:
- Verified that the token is being passed correctly in the header.
- Checked that the
CustomAuthMiddleware
is correctly decoding the JWT and fetching the user. - Confirmed that the JWT works correctly for API requests (outside of WebSocket).
- Debugged the middleware to ensure that the token is being received and decoded.
Expected Behavior:
The WebSocket connection should authenticate the user correctly using the JWT token and allow them to connect and chat with other users in real-time.
Question:
What could be causing the WebSocket to reject the connection with the message "Unauthenticated user attempted to connect."
even though a valid JWT token is being sent in the header? Is there an issue with how I am handling the token authentication for WebSockets in Django Channels, or is there something else I might be missing?