Django Rest Framework authentication and Django Middleware: Why request.user is anonymous?
When using DRF and in in the context of Django middleware I have often found to be the case that request.user is an AnonymousUser. Why is that?
It is because the DRF authentication classes are executed after all the normal Django middleware are already in their get_response stage.
class DjangoMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# request.user is AnonymousUser
# this will eventually call DRF
response = get_response(request)
# request.user should be defined at this point
return response
If you want to setup a logger enriched with user meta data it doesn’t not make sense to wait for the response from DRF to setup things properly… So what can you do to fix that?
A simple way to hook into DRF auth system is to subclass the authentication mechanism that you use. For example let’s say you are using Django REST Framework Simple JWT plugin you can subclass it this way:
# project/jwt_auth.py
from rest_framework_simplejwt.authentication import JWTAuthentication
# request_context is a ContextVar we use to share state
from project.request_context import request_context
import sentry_sdk
class JWTAuthenticationWithSidEffects(JWTAuthentication):
"""
DRF does its own authentication that happens after Django middleware
https://stackoverflow.com/a/53198640
"""
def authenticate(self, request):
user_auth_tuple = super().authenticate(request)
if user_auth_tuple is None:
return
(user, token) = user_auth_tuple
# Then you can do what need to do with the user here
context = request_context.get()
context["user_id"] = user and user.id
context["language"] = user and not user.is_anonymous and user.language
request_context.set(context)
sentry_sdk.set_context("request", context)
return (user, token)
Then substitute the old authentication class with your own
REST_FRAMEWORK = {
"COERCE_DECIMAL_TO_STRING": False,
"DEFAULT_AUTHENTICATION_CLASSES": [
- "rest_framework_simplejwt.authentication.JWTAuthentication",
+ "project.jwt_auth.JWTAuthenticationWithSidEffects",
]
}
You should now be able to use the request_context ContextVar within DRF. For example you could use it in a special logger or have a DRF Serializer that takes advantages of the user language to return a specific translations. Here is an example on how to use it:
from django.db import models
from rest_framework import serializers
from project.request_context import request_context
class DogBreed(models.Model):
name_fr = models.CharField(max_length=255)
name_de = models.CharField(max_length=255)
name_en = models.CharField(max_length=255)
class DogBreedSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField()
def get_name(self, instance):
if rc := request_context.get():
lang = rc.get("language") or "en"
else:
lang = "en"
return getattr(instance, f"name_{lang}")
class Meta:
model = DogBreed
fields = ["name"]
And voilà! that should work.