Skip to content
This repository has been archived by the owner on May 26, 2020. It is now read-only.

request.user shows AnonymousUser in middleware #45

Closed
marcelkornblum opened this issue Nov 6, 2014 · 30 comments
Closed

request.user shows AnonymousUser in middleware #45

marcelkornblum opened this issue Nov 6, 2014 · 30 comments

Comments

@marcelkornblum
Copy link

I'm not sure if this is an issue or just because of the implementation, but I was using middleware to log user_id against records being edited, and I couldn't get request.user at the middleware level (and so of course request.user.is_authorized() returned False).

The middleware was the last one on the list, and authorization was working (I would get a 403 requesting assets without my token header). I tried putting 'rest_framework_jwt.authentication.JSONWebTokenAuthentication' at the top of DEFAULT_AUTHENTICATION_CLASSES, and also making it the only item in the tuple; neither worked.

I've managed to work around my issue by accessing the header and using the tools directly in the app to retrieve the user; essentially I copied chunks of your code into my middleware, but I thought you should know :)

If you're interested, here's the middleware:

from django.db.models import signals
from django.utils.functional import curry
from django.utils import timezone
from threading import local

import jwt
from rest_framework.authentication import get_authorization_header
from rest_framework_jwt.settings import api_settings
from django.conf import settings
try:
    from django.contrib.auth import get_user_model
except ImportError:  # Django < 1.5
    from django.contrib.auth.models import User
else:
    User = get_user_model()

_user = local()

class AuditMiddleware(object):
    def process_request(self, request):
        if not request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
            if hasattr(request, 'user') and request.user.is_authenticated():
                user = request.user
            else:
                user = self.get_user_from_auth_header(request)
                if user is not None:
                    request.user = user

            _user.value = user

            mark_whodid = curry(self.mark_whodid, _user.value)
            signals.pre_save.connect(mark_whodid,  dispatch_uid = (self.__class__, request,), weak = False)

    def process_response(self, request, response):
        signals.pre_save.disconnect(dispatch_uid =  (self.__class__, request,))
        return response

    def mark_whodid(self, user, sender, instance, **kwargs):
        # user logging here

    def get_user_from_auth_header(self, request):
        try:
            auth_keyword, token = get_authorization_header(request).split()
            jwt_header, claims, signature = token.split('.')

            try:
                payload = api_settings.JWT_DECODE_HANDLER(token)
                try:
                    user_id = api_settings.JWT_PAYLOAD_GET_USER_ID_HANDLER(payload)

                    if user_id:
                        user = User.objects.get(pk=user_id, is_active=True)
                        return user
                    else:
                        msg = 'Invalid payload'
                        return None
                except User.DoesNotExist:
                    msg = 'Invalid signature'
                    return None

            except jwt.ExpiredSignature:
                msg = 'Signature has expired.'
                return None
            except jwt.DecodeError:
                msg = 'Error decoding signature.'
                return None
        except ValueError:
            return None
@brunifrancesco
Copy link

Exactly what I were looking for.
I think that's impossible for now retrieving who's the user performs the request via the jwt header, beacause you need to use the "authentication_classes" attribute in the ApiView/ViewSet extended class to retrieve the user.

@jpadilla
Copy link
Owner

This should be the same behavior with any Authentication class for DRF, since it doesn't happen at a Middleware layer, but at the view layer. An idea would be to perhaps create a custom DRF authentication class or even a view mixin to do whatever you're already doing in your AuditMiddleware.

@jpadilla
Copy link
Owner

Closing this for now.

@cancan101
Copy link
Contributor

This happens because the AuthenticationMiddleware, which sets the user on the request expects to use the session store: https://github.com/django/django/blob/0f7f5bc9e7a94ab91c2b3db29ef7cf000eff593f/django/contrib/auth/__init__.py#L159.

@cancan101
Copy link
Contributor

This is how I fixed it. I created a new middleware:

from rest_framework.request import Request
from django.utils.functional import SimpleLazyObject
from django.contrib.auth.middleware import get_user

def get_user_jwt(request):
    user = get_user(request)
    if user.is_authenticated():
        return user
    try:
        user_jwt = JSONWebTokenAuthentication().authenticate(Request(request))
        if user_jwt is not None:
            return user_jwt[0]
    except:
        pass
    return user


class AuthenticationMiddlewareJWT(object):
    def process_request(self, request):
        assert hasattr(request, 'session'), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."

        request.user = SimpleLazyObject(lambda: get_user_jwt(request))

@jpadilla
Copy link
Owner

@cancan101 interesting, thanks for sharing

@klahnen
Copy link

klahnen commented Sep 26, 2015

I think you missed this import:

from rest_framework_jwt.authentication import JSONWebTokenAuthentication

@klahnen
Copy link

klahnen commented Sep 26, 2015

Hi,

I know this post is old, I had same problem using django-audit-log and I applied @cancan101 patch to django-audit-log If you are interested here is the issue I reported:
vvangelovski/django-audit-log#28

@Oritk
Copy link

Oritk commented Oct 27, 2015

i used @cancan101 solution. Thanks

@klahnen
Copy link

klahnen commented Oct 27, 2015

Django-audit-log already added a fix. So you dont need to monkey patch.

@Oritk
Copy link

Oritk commented Oct 27, 2015

Thanks @klahnen - but im not using the audit_log, i need the request.user in a prprietary middleware

@hoIIer
Copy link
Contributor

hoIIer commented Nov 25, 2015

landed here.. still can't get it working.. I noticed that when making a raw $.ajax POST request from my ember app that requires the user being authenticated, despite my jwt token being passed in the AuthorizationHeader, on the backend request.user == AnonymousUser.. I can't figure out how to populate it or why! ugh

@vannitotaro
Copy link

@cancan101 thank you for your clean solution, it's working great with the missing import pointed out by @klahnen

@tohyongcheng
Copy link

👍 thanks @cancan101, i needed this too

@joshgeller
Copy link

Thanks @cancan101

@AndrewJHart
Copy link

Thanks @cancan101 and everyone else 👍 However my situation was a little different since we had no use for the session store or django.contrib.auth -- we are using a lightweight version of django as a microservice for managing user authentication. However, we had 2 views that were non-api that depended on the @login_required decorator and they would fail obviously due to the AnonymousUser issue mentioned above. FYI we are also using django for all the other services but using builtin authorization so the service doesn't need to reach out to the users/auth service each time.

Ergo, I borrowed some of @cancan101 's code and made some tweaks.

from django.utils.functional import SimpleLazyObject
from django.contrib.auth.models import AnonymousUser

from rest_framework.request import Request
from rest_framework_jwt.authentication import JSONWebTokenAuthentication


def get_user_jwt(request):
    """
    Replacement for django session auth get_user & auth.get_user
     JSON Web Token authentication. Inspects the token for the user_id,
     attempts to get that user from the DB & assigns the user on the
     request object. Otherwise it defaults to AnonymousUser.

    This will work with existing decorators like LoginRequired  ;)

    Returns: instance of user object or AnonymousUser object
    """
    user = None
    try:
        user_jwt = JSONWebTokenAuthentication().authenticate(Request(request))
        if user_jwt is not None:
            # store the first part from the tuple (user, obj)
            user = user_jwt[0]
    except:
        pass

    return user or AnonymousUser()


class JWTAuthenticationMiddleware(object):
    """ Middleware for authenticating JSON Web Tokens in Authorize Header """
    def process_request(self, request):
        request.user = SimpleLazyObject(lambda : get_user_jwt(request))

This forces all requests to have a valid JWT before they can access any of the DRF api resources with the added benefit of still functioning properly with the @login_required decorator. Ergo, this makes adding something like django-oauth-toolkit a cinch (with a little customization to make it return JWT instead of a stored standard token).

We have actually built a very lightweight version of django skeleton that we have used for many microservices - each using separate datastores - to much success. I'm actually very impressed with the performance.

If anyone has any questions, suggestions, or improvements I'd love to hear them.

Thanks.

P.S. @jpadilla Is there a chance we can get something like this added into the core package as an option for those users who depend on request.user to an actual User instance vs AnonymousUser - I know its not relative to all cases but it would be very useful. I'd be happy to write the docs and answer any questions about how we're using django for microservices very successfully.

@hoIIer
Copy link
Contributor

hoIIer commented May 5, 2016

should this be added to the package as an optional include?

@hoIIer
Copy link
Contributor

hoIIer commented May 5, 2016

@AndrewJHart does it matter where in the middleware ordering this goes?

@rserro
Copy link

rserro commented Oct 21, 2016

Django 1.10 style without suppressing exceptions.

# -*- coding: utf-8 -*-

from django.contrib.auth.middleware import get_user
from django.utils.functional import SimpleLazyObject
from rest_framework_jwt.authentication import JSONWebTokenAuthentication


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

    def __call__(self, request):
        request.user = SimpleLazyObject(lambda: self.__class__.get_jwt_user(request))
        return self.get_response(request)

    @staticmethod
    def get_jwt_user(request):
        user = get_user(request)
        if user.is_authenticated:
            return user
        jwt_authentication = JSONWebTokenAuthentication()
        if jwt_authentication.get_jwt_value(request):
            user, jwt = jwt_authentication.authenticate(request)
        return user

@tonyzhu
Copy link

tonyzhu commented Nov 11, 2016

so this will authenticate twice in a request ?

@mapeveri
Copy link

Hi everyone, i have this code:

from django.contrib.auth.middleware import get_user
from django.utils.functional import SimpleLazyObject
from rest_framework_jwt.authentication import JSONWebTokenAuthentication


class AuthenticationMiddlewareJWT(MiddlewareMixin):
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        request.user = SimpleLazyObject(lambda: self.__class__.get_jwt_user(request))
        return self.get_response(request)

    @staticmethod
    def get_jwt_user(request):
        user = get_user(request)
        if user.is_authenticated:
            return user
        jwt_authentication = JSONWebTokenAuthentication()
        if jwt_authentication.get_jwt_value(request):
            user, jwt = jwt_authentication.authenticate(request)
        return user

And this jwt_authentication.get_jwt_value(request) always return None.

My configurations is:

'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),

What can be wrong?. Thanks!

@blueyed
Copy link
Contributor

blueyed commented Sep 30, 2017

@mapeveri
You should create a new issue.

@QuestMi
Copy link

QuestMi commented Jul 12, 2018

It is so cool@cancan101

@matthewcornell
Copy link

Hi Folks. I'm having this issue too. I'm new to django-rest-framework-jwt, and somewhat new to DRF. Two questions:

  • I'm confused about why the above middleware solutions are required at all. As a new user, I expected all this to be working already. How is it useful to and end users when the request.user is not extracted from the JWT token?
  • I saw this mentioned: Ensure request.user is available to response middleware. encode/django-rest-framework#2155 . I'm using DRF 3.8.2, which is more recent than the change there, but still no joy. Does this DRF change solve the problem?

Thank you!

@gabn88
Copy link

gabn88 commented Feb 26, 2019

@matthewcornell It doesn't. Which is shit, because with the authentication snippet of @cancan101 above you'll still need two database calls, because the user is authenticated twice instead of once. But I also just did it like this, because the authentication is fast and it felt like a premature optimization. But still, it does not feel very elegant for something as simple as authentication.

Also please note the root cause of this issue is DRF, and not Django itself.

By the way, the following is the same snippet. But then for djangorestframework-simple-jwt:

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

    def __call__(self, request):
        request.user = SimpleLazyObject(lambda: self.__class__.get_jwt_user(request))
        return self.get_response(request)

    @staticmethod
    def get_jwt_user(request):
        from rest_framework_simplejwt.authentication import JWTAuthentication
        user = get_user(request)
        if user.is_authenticated:
            return user
        try:
            user_jwt = JWTAuthentication().authenticate(Request(request))
            if user_jwt is not None:
                return user_jwt[0]
        except:
            pass
        return user # AnonymousUser

@matthewcornell
Copy link

matthewcornell commented May 21, 2019

Following up on @rserro 's solution, I get a rest_framework.exceptions.AuthenticationFailed error when the token is expired - at the line user, jwt = jwt_authentication.authenticate(request). Has anyone encountered this problem? I can catch the error and return user (AnonymousUser), but I'd prefer to return a response that indicates the root problem - expiration. Thank you.

@matthewcornell
Copy link

@gabn88 Thank you. Question: In your code, you use djangorestframework-simple-jwt's JWTAuthentication. What does that get you?

@yerycs
Copy link

yerycs commented Nov 30, 2019

This is how I fixed it. I created a new middleware:

from rest_framework.request import Request
from django.utils.functional import SimpleLazyObject
from django.contrib.auth.middleware import get_user

def get_user_jwt(request):
    user = get_user(request)
    if user.is_authenticated():
        return user
    try:
        user_jwt = JSONWebTokenAuthentication().authenticate(Request(request))
        if user_jwt is not None:
            return user_jwt[0]
    except:
        pass
    return user


class AuthenticationMiddlewareJWT(object):
    def process_request(self, request):
        assert hasattr(request, 'session'), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."

        request.user = SimpleLazyObject(lambda: get_user_jwt(request))

Not working.
It doesn't work and I gets still AnonymousUser.
How can I get request.user?

@bennett-jacob
Copy link

@yerycs

  1. Check your middleware order. You may need to move your auth middleware down.
  2. The response you referenced is from 2015. That's an unsupported version of Django. Try this one: request.user shows AnonymousUser in middleware #45 (comment)

@divum-selvakumar
Copy link

This is how I fixed it. I created a new middleware:

from rest_framework.request import Request
from django.utils.functional import SimpleLazyObject
from django.contrib.auth.middleware import get_user

def get_user_jwt(request):
    user = get_user(request)
    if user.is_authenticated():
        return user
    try:
        user_jwt = JSONWebTokenAuthentication().authenticate(Request(request))
        if user_jwt is not None:
            return user_jwt[0]
    except:
        pass
    return user


class AuthenticationMiddlewareJWT(object):
    def process_request(self, request):
        assert hasattr(request, 'session'), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."

        request.user = SimpleLazyObject(lambda: get_user_jwt(request))

Thanks i just customize your idea. It helps thank you

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests