Search code examples
djangodjango-channels

Django Channels - Custom Authentication Middleware raises: 'coroutine' object is not callable


I have created a custom token authentication middleware.

from rest_framework.authtoken.models import Token
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
from asgiref.sync import sync_to_async


class TokenAuthMiddleware:
    """
    Token authorization middleware for Django Channels 2
    """

    def __init__(self, inner):
        self.inner = inner

    def __call__(self, scope):
        # Close old database connections to prevent usage of timed out connections
        sync_to_async(close_old_connections)()

        headers = dict(scope['headers'])
        try:
            token_name, token_key = headers[b'sec-websocket-protocol'].decode().split(', ')
            
            if token_name == 'Token':
                token = sync_to_async(Token.objects.get, thread_sensitive=True)(key=token_name)

                scope['user'] = token.user
            
            else:
                scope['user'] = AnonymousUser()

        except Token.DoesNotExist:
            scope['user'] = AnonymousUser()

        return self.inner(scope)

When I run it, an exception happens when I run scope['user'] = token.user

[Failure instance: Traceback: <class 'AttributeError'>: 'coroutine' object has no attribute 'user'

I tried awaiting the Token query like this:

token = await sync_to_async(Token.objects.get, thread_sensitive=True)(key=token_name)

and I added async in front of the __call__ function, but then the following error is raised before any of the code inside the __call__ function runs:

[Failure instance: Traceback: <class 'TypeError'>: 'coroutine' object is not callable

I am using Django v3.0.6 and Django Channels v2.4.0


Solution

  • Here is the solution that worked for me:

    from rest_framework.authtoken.models import Token
    from django.contrib.auth.models import AnonymousUser
    from channels.db import database_sync_to_async
    
    
    @database_sync_to_async
    def get_user(token):
        try:
            return Token.objects.get(key=token).user
        except Token.DoesNotExist:
            return AnonymousUser()
    
    class TokenAuthMiddleware:
        """
        Token authorization middleware for Django Channels 2
        """
    
        def __init__(self, inner):
            # Store the ASGI application we were passed
            self.inner = inner
    
        def __call__(self, scope):
    
            return TokenAuthMiddlewareInstance(scope, self)
    
    class TokenAuthMiddlewareInstance:
        """
        Inner class that is instantiated once per scope.
        """
    
        def __init__(self, scope, middleware):
            self.middleware = middleware
            self.scope = dict(scope)
            self.inner = self.middleware.inner
    
        async def __call__(self, receive, send):
            headers = dict(self.scope['headers'])
    
            token_name, token_key = headers[b'sec-websocket-protocol'].decode().split(', ')
            
            if token_name == 'Token':
                self.scope['user'] = await get_user(token_key)
    
            else:
                self.scope['user'] = AnonymousUser()
    
            # Instantiate our inner application
            inner = self.inner(self.scope)
    
            return await inner(receive, send)