I'm so confused why my middleware isn't correctly getting a user based token query by key.
First I checked in my consumer, however the user in self.scope
was an AnonymousUser
(seen below in the code). This is reiterated when I set a foreign key in my receiver function, which raises the error: ValueError: Cannot assign "<django.contrib.auth.models.AnonymousUser object at 0x10943cc10>": "ChatMessage.user" must be a "User" instance.
Hence, I'm assuming the error is my middleware. I experimented with putting a print statement in middleware.TokenAuthMiddleware.__call__
after the get_user
function is awaited and called, however, unexpectedly, the function returns an AnonymousUser
. Both the user and the associated Token are created in the testcase's setUp
and it's that token's key which is passed to the function.
Is the token string becoming malformed in format_querystring
? The function is tested and passed, so I do not believe the token is becoming malformed there.
One thought is because the user and the token are being created in setUp
, which is not async
, it somehow is not aligned with the processes of the database look up in get_user
. However changing that to async
and wrapping the database calls in database_sync_to_async
did not fix it, so I don't believe it's that either.
One idea is that not setting up my application correctly in the test, however based on their tests, it seems correct.
# middleware.py
@database_sync_to_async
def get_user(token_key):
"""
Get token from token key
Parameters:
token_key (str): Token key string
Returns:
if found, return associated User. Else, AnonymousUser
"""
try:
token = Token.objects.get(key=token_key)
except Token.DoesNotExist:
return AnonymousUser()
else:
return token.user
class TokenAuthMiddleware(BaseMiddleware):
"""
Token authorization middleware for Django Channels
E.g.
ws://localhost:8000/<route>/?token=<token_of_the_user>
Based on:
https://stackoverflow.com/a/49372334/5574063
https://stackoverflow.com/a/65654519/5574063
https://gist.github.com/rluts/22e05ed8f53f97bdd02eafdf38f3d60a#gistcomment-3166469
TODO:
implement:
https://gist.github.com/rluts/22e05ed8f53f97bdd02eafdf38f3d60a#gistcomment-3174829
https://stackoverflow.com/a/4361358/5574063
"""
def __init__(self, inner):
self.inner = inner
async def __call__(self, scope, receive, send):
close_old_connections()
query = self.format_querystring(scope['query_string'])
token_key = query.get('token')
scope['user'] = await get_user(token_key)
return await super(TokenAuthMiddleware, self).__call__(scope, receive, send)
def format_querystring(self, qs):
"""
Convert byte querystring into dictionary.
NOTE: will only take the first value.
Parameters:
qs (bstr): Querystring as bytes
Returns:
Dict of querystring
"""
return {k: v[0] for k, v in dict(parse_qs(qs.decode().lower())).items()}
def TokenAuthMiddlewareStack(inner):
return TokenAuthMiddleware(inner)
# consumer.py
...
async def connect(self):
good_user = 'user' in self.scope and self.scope['user'].is_authenticated
print(good_user)
...
>>> False
# test.py
async def test_receive(self):
# self.user and rest_framework Token created
self.application = TokenAuthMiddlewareStack(URLRouter(websocket_urlpatterns))
self.route = '/ws/chat/test-room/?token={}'.format(self.user.auth_token.key)
communicator = WebsocketCommunicator(self.application, self.route)
connected, _ = await communicator.connect()
...
I'm using channels==3.0.3
and Django==3.2.5
EDIT:
Narrowed down the problem. The Token queryset is empty when it reaches get_user
. E.i.
# printed from the test
<QuerySet [<Token: bca50384e55d88fdc109c73b29c5d0745c5dd379>]>
# printed from within the function
<QuerySet []>
Why would the queryset empty for the middleware/consumer?