[Python] Why is my WebSocket connection being rejected with "Unauthenticated user" in Django...

Discussão em 'Python' iniciado por Stack, Novembro 28, 2024.

  1. Stack

    Stack Membro Participativo

    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 the auth_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:

    1. Verified that the token is being passed correctly in the header.
    2. Checked that the CustomAuthMiddleware is correctly decoding the JWT and fetching the user.
    3. Confirmed that the JWT works correctly for API requests (outside of WebSocket).
    4. 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?

    Continue reading...

Compartilhe esta Página