51

I'm trying to make middleware which alters some fields for the user based on subdomain, etc...

The only problem is the request.user always comes in as AnonymousUser within the middleware, but is then the correct user within the views. I've left the default authentication and session middleware django uses within the settings.

There is a similar question here: Django, request.user is always Anonymous User But doesn't overly answer the total question because I'm not using different authentication methods, and djangos authentication is running before I invoke my own middleware.

Is there a way, while using DRF, to get the request.user within the middleware? I'll show some sample code here:

class SampleMiddleware(object):

  def process_view(self, request, view_func, view_args, view_kwargs):
    #This will be AnonymousUser.  I need it to be the actual user making the request.
    print (request.user)    

  def process_response(self, request, response):
    return response

with process_request:

class SampleMiddleware(object):

  def process_request(self, request):
    #This will be AnonymousUser.  I need it to be the actual user making the request.
    print (request.user)    

  def process_response(self, request, response):
    return response
Francisco
  • 10,918
  • 6
  • 34
  • 45
Jordan
  • 1,969
  • 2
  • 18
  • 19
  • 1
    the order of the middleware is important. make sure the authentication middleware is listed before yours in the settings – Gabriel Amram Oct 07 '14 at 16:32
  • 5
    I've included it at the end of the middleware, after django has run its default ones. (Auth and Session ones) – Jordan Oct 07 '14 at 16:33
  • Have your tried with [process_request](https://docs.djangoproject.com/en/dev/topics/http/middleware/#process-request) instead process_view? – dani herrera Oct 07 '14 at 16:57
  • 1
    There must be something else going on here, some additional code that you haven't posted. – dgel Oct 07 '14 at 16:58
  • I have tried process_request as well, process_view was in there from me trying multiple things. I agree there must be more going on here. I'll dig deeper and try to provide some more applicable code – Jordan Oct 07 '14 at 17:08
  • 1
    Hey yeah, sorry that was me switching around process_request and process_view. It's updated now. – Jordan Oct 07 '14 at 17:31
  • 1
    django version? How do you do authorization? post MIDDLEWARE_CLASSES settings too. – Alex Lisovoy Oct 07 '14 at 18:38

8 Answers8

55

I've solved this problem by getting DRF token from the requests and loading request.user to the user associated to that model.

I had the default django authentication and session middleware, but it seems DRF was using it's token auth after middleware to resolve the user (All requests were CORS requests, this might have been why). Here's my updated middleware class:

from re import sub
from rest_framework.authtoken.models import Token
from core.models import OrganizationRole, Organization, User

class OrganizationMiddleware(object):

  def process_view(self, request, view_func, view_args, view_kwargs):
    header_token = request.META.get('HTTP_AUTHORIZATION', None)
    if header_token is not None:
      try:
       token = sub('Token ', '', header_token)
        token_obj = Token.objects.get(key = token)
        request.user = token_obj.user
      except Token.DoesNotExist:
        pass
    #This is now the correct user
    print (request.user)

This can be used on process_view or process_request as well.

Hopefully this can help someone out in the future.

Abhijith Konnayil
  • 4,067
  • 6
  • 23
  • 49
Jordan
  • 1,969
  • 2
  • 18
  • 19
  • I was in the same situation and this worked perfectly! I'm left wondering, though, if this wouldn't lead to the token being processed a second by whatever runs after the middleware to authenticate the token? Is that bad? I'm too new to DRF to know either way. – IAmKale Apr 15 '15 at 17:04
  • 4
    Hey, so I've found out this isn't the best way to implement this. The right way to implement it is to include rest_frameworks authentication middleware to the AUTHENTICATION_BACKENDS within settings. ```'rest_framework.authentication.TokenAuthentication'``` – Jordan Jun 25 '15 at 03:31
  • @Jordan can you elaborate on your last comment please? I have the same issue, and tried to add TokenAuthentication to AUTHENTICATION_BACKENDS, and that did not solve my issue. How did adding it to AUTHENTICATION_BACKENDS help you? Thanks – baselq Jul 28 '15 at 22:54
  • @baselq I actually reverted back to this, because invalid tokens were just getting rejected rather than deleting them on login, and then we had to tell users to delete their cookie in order to login. – Jordan Aug 12 '15 at 15:20
  • @Jordan please see my answer above, this seems unrelated to the question. – Daniel Dror Dec 22 '16 at 11:17
  • Thanks @Jordan: You've saved my day – spyshiv Jun 10 '17 at 20:14
  • While this works, imo @DanielDubovski has a better solution in his answer below. – Sune Kjærgård Dec 06 '17 at 13:30
  • thanks but this is not a proper solution, kind of hack – TraviJuu Sep 10 '18 at 16:15
  • `token = sub('token ', '', request.META.get('HTTP_AUTHORIZATION', None))` worked for me. It should be `'token '` and not `'Token '` – Anoop Nair May 13 '19 at 10:47
  • This will make unnecessary db call for every request :( – Dinesh Singh Sep 20 '21 at 18:56
29

Came across this today while having the same problem.

TL;DR;

Skip below for code example


Explanation

Thing is DRF have their own flow of things, right in the middle of the django request life-cycle.

So if the normal middleware flow is :

  1. request_middleware (before starting to work on the request)
  2. view_middleware (before calling the view)
  3. template_middleware (before render)
  4. response_middleware (before final response)

DRF code, overrides the default django view code, and executes their own code.

In the above link, you can see that they wrap the original request with their own methods, where one of those methods is DRF authentication.

So back to your question, this is the reason using request.user in a middleware is premature, as it only gets it's value after view_middleware** executes.

The solution I went with, is having my middleware set a LazyObject. This helps, because my code (the actual DRF ApiVIew) executes when the actual user is already set by DRF's authentication. This solution was proposed here together with a discussion.

Might have been better if DRF had a better way to extend their functionality, but as things are, this seems better than the provided solution (both performance and readability wise).


Code Example

from django.utils.functional import SimpleLazyObject

def get_actual_value(request):
    if request.user is None:
        return None

    return request.user #here should have value, so any code using request.user will work


class MyCustomMiddleware(object):
    def process_request(self, request):
        request.custom_prop = SimpleLazyObject(lambda: get_actual_value(request))
Daniel Dror
  • 2,304
  • 28
  • 30
11

The accepted answer only takes TokenAuthentication in consideration - in my case, there are more authentication methods configured. I thus went with initializing the DRF's Request directly which invokes DRF's authentication machinery and loops through all configured authentication methods.

Unfortunately, it still introduces an additional load on the database since the Token object must be queried (the accepted answer has this problem as well). The trick with SimpleLazyObject in this answer is a much better solution, but it didn't work for my use case because I need the user info in the middleware directly - I'm extending the metrics in django_prometheus and it processes the request before get_response is called.

from rest_framework.request import Request as RestFrameworkRequest
from rest_framework.views import APIView

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

    def __call__(self, request):
        drf_request: RestFrameworkRequest = APIView().initialize_request(request)
        user = drf_request.user
        ...
        return self.get_response(request)
hoefling
  • 59,418
  • 12
  • 147
  • 194
  • 2
    Works with SimpleJWT – kommradHomer Mar 02 '22 at 00:48
  • 2
    I initially went with this solution as it is the most neat one. But it caused some issues: 1) logging to Django admin (user is always anon and never logs) 2) when implemented custom DRF auth class, the middleware caused 500 responses when auth fails. – Salma Hassan May 12 '22 at 11:51
  • 1
    @SalmaHassan sure, this is not a complete solution, I omitted all of the boilerplate code for brevity. You have to wrap `APIView().initialize_request()` in a try/catch block and handle `APIException`s, depending on the middleware context. Regarding admin interface - this shouldn't happen, can't confirm that with the admin app in my projects. – hoefling May 12 '22 at 12:14
  • @hoefling I see. Just a thing to point out that `drf_request.user` should be inside the try/catch block too because I discovered that `initialize_request` actually doesn't perform any authentication and doesn't set the user, this is handled in the `@property` method of the `user` instead [code](https://github.com/encode/django-rest-framework/blob/cdc956a96caafddcf4ecaf6218e340ebb3ce6d72/rest_framework/request.py#L220) – Salma Hassan May 16 '22 at 09:52
7

Based on Daniel Dubovski's very elegant solution above, here's an example of middleware for Django 1.11:

from django.utils.functional import SimpleLazyObject
from organization.models import OrganizationMember
from django.core.exceptions import ObjectDoesNotExist


def get_active_member(request):
    try:
        active_member = OrganizationMember.objects.get(user=request.user)
    except (ObjectDoesNotExist, TypeError):
        active_member = None
    return active_member


class OrganizationMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response


    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        request.active_member = SimpleLazyObject(lambda: get_active_member(request))

        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.
        return response
Sune Kjærgård
  • 688
  • 6
  • 11
5

Daniel Dubovski's solution is probably the best in most cases.

The problem with the lazy object approach is if you need to rely on the side effects. In my case, I need something to happen for each request, no matter what.

If I'd use a special value like request.custom_prop, it has to be evaluated for each request for the side effects to happen. I noticed that other people are setting request.user, but it doesn't work for me since some middleware or authentication class overwrites this property.

What if DRF supported its own middleware? Where could I plug it in? The easiest way in my case (I don't need to access the request object, only the authenticated user) seems to be to hook into the authentication class itself:

from rest_framework.authentication import TokenAuthentication

class TokenAuthenticationWithSideffects(TokenAuthentication):

    def authenticate(self, request):
        user_auth_tuple = super().authenticate(request)

        if user_auth_tuple is None:
            return
        (user, token) = user_auth_tuple

        # Do stuff with the user here!

        return (user, token)

Then I could just replace this line in my settings:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        #"rest_framework.authentication.TokenAuthentication",
        "my_project.authentication.TokenAuthenticationWithSideffects",
    ),
    # ...
}

I'm not promoting this solution, but maybe it will help someone else.

Pros:

  • It to solves this specific problem
  • There's no double authentication
  • Easy to maintain

Cons:

  • Not tested in production
  • Things happen in an unexpected place
  • Side effects...
André Laszlo
  • 15,169
  • 3
  • 63
  • 81
  • 1
    Thanks a lot for this answer. I created a small article on medium using your example https://batiste.medium.com/django-rest-framework-authentication-and-django-middleware-why-request-user-is-anonymous-d9d62bddf4f6 – Batiste Bieler Jan 04 '23 at 14:57
3

I know it's not exactly answering the 'can we access that from the middleware' question, but I think it's a more elegant solution VS doing the same work in the middleware VS what DRJ does in its base view class. At least for what I needed, it made more sense to add here.

Basically, I'm just overriding the method 'perform_authentication()' from DRF's code, since I needed to add more things related to the current user in the request. The method just originally call 'request.user'.

class MyGenericViewset(viewsets.GenericViewSet):

    def perform_authentication(self, request):
        request.user

        if request.user and request.user.is_authenticated():
            request.my_param1 = 'whatever'

After that in your own views, instead of settings APIView from DRF as a parent class, simply set that class as a parent.

mrmuggles
  • 2,081
  • 4
  • 25
  • 44
2

I wasn't quite happy with the solutions out there. Here's a solution that uses some DRF internals to make sure that the correct authentication is applied in the middleware, even if the view has specific permissions classes. It uses the middleware hook process_view which gives us access to the view we're about to hit:

class CustomTenantMiddleware():
    def process_view(self, request, view_func, view_args, view_kwargs):
        # DRF saves the class of the view function as the .cls property
        view_class = view_func.cls
        try:
            # We need to instantiate the class
            view = view_class()
            # And give it an action_map. It's not relevant for us, but otherwise it errors.
            view.action_map = {}
            # Here's our fully formed and authenticated (or not, depending on credentials) request
            request = view.initialize_request(request)
        except (AttributeError, TypeError):
            # Can't initialize the request from this view. Fallback to using default permission classes
            request = APIView().initialize_request(request)

        # Here the request is fully formed, with the correct permissions depending on the view.

Note that this doesn't avoid having to authenticate twice. DRF will still happily authenticate right afterwards.

Gustav Wengel
  • 345
  • 3
  • 11
  • this looks like a good solution, could it cause any side effect, since it is initializing "DRF request", inside middleware? is it safe? – Harsh Bhikadia Jun 16 '21 at 11:48
0

I had the same issue and decided to change my design. Instead of using a Middleware I simply monkey-patch rest_framework.views.APIView.

In my case I needed to patch check_permissions but you can patch whatever fits your problem. Have a look at the the source code.

settings.py

INSTALLED_APPS = [
    ..
    'myapp',
]

myapp/patching.py

import sys

from rest_framework.views import APIView as OriginalAPIView


class PatchedAPIView(OriginalAPIView):
    def check_permissions(self, request):
        print(f"We should do something with user {request.user}"
        return OriginalAPIView.check_permissions(self, request)


# We replace the Django REST view with our patched one
sys.modules['rest_framework'].views.APIView = PatchedAPIView

myapp/__init__.py

from .patching import *
Pithikos
  • 18,827
  • 15
  • 113
  • 136