Search code examples
pythondjangodjango-rest-frameworkdjango-middlewaredjango-rest-framework-simplejwt

How to modify django's request.user in a Middleware?


What I'm trying to do is to detect the type of logged-in user and then setting a .profile parameter to request.user, so I can use it by calling request.user.profile in my views.

To do this, I've wrote a Middleware as follows:

class SetProfileMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

        user, token = JWTAuthentication().authenticate(request)
        profile_type = token.payload.get("profile_type", None)

        request.user.profile = User.get_profile(profile_type, request.user)
        request.user.profile_type = profile_type
        
        # Works Here
        print("-" * 20)
        print(type(request.user)) # <class 'django.utils.functional.SimpleLazyObject'>
        print('Process Request ->', request.user.profile)


        response = self.get_response(request)

        # Does not work here
        print("-" * 20)
        print(type(request.user)) #  <class 'users.models.User'>
        print('Process Response ->', request.user.profile)


        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        # Works here
        print("-" * 20)
        print(type(request.user)) # <class 'django.utils.functional.SimpleLazyObject'>
        print('Process View ->', request.user.profile)

Now I can access request.user.profile in process_view however it does not exists in my views and is causing an AttributeError stating that 'User' object has no attribute 'profile'.

Seems my request.user is being overwritten somewhere before hitting the view.


Note that I'm using Django Rest Framework, here is my view:

class ProfileAPIView(generics.RetrieveUpdateAPIView):
    serializer_class = ProfileSerializer

    def get_object(self):
        obj = self.request.user.profile # Raise the `AttributeError`
        self.check_object_permissions(self.request, obj)
        return obj

Here is my settings.py:

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

LOCAL_MIDDLEWARE = [
    "users.middleware.SetProfileMiddleware",
]

MIDDLEWARE = MIDDLEWARE + LOCAL_MIDDLEWARE

REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
    "DEFAULT_RENDERER_CLASSES": (
        "rest_framework.renderers.JSONRenderer",
        "rest_framework.renderers.BrowsableAPIRenderer",
    ),
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ],
}

SIMPLE_JWT = {
    "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(minutes=45),
    "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.SlidingToken",),
}

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

AUTH_USER_MODEL = "users.User"

LOGIN_REDIRECT_URL = "admin/"

Solution

  • After spending hours to figure out what is going on, turned out that SimpleJWT's JWTAuthentication.authenticate() method gets called just before the request hits the View, overwriting the request.user attribute.

    So instead of trying to add the profile to the request.user using a middleware, I ended-up customizing JWTAuthentication.authentication() method:

    class CustomAuth(JWTAuthentication):
        def authenticate(self, request):
    
            user, token = super().authenticate(request)
    
            profile_type = token.payload.get("profile_type", None)
            user.profile = User.get_profile((profile_type, user)
            user.profile_type = profile_type
    
            return user, token
    

    settings.py:

    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": [
            "users.authentication.CustomAuth"
        ],
    }