jazzband / django-oauth-toolkit

OAuth2 goodies for the Djangonauts!
https://django-oauth-toolkit.readthedocs.io
Other
3.13k stars 792 forks source link

request.user in custom middleware gives AnonymousUser #821

Closed anuj9196 closed 4 years ago

anuj9196 commented 4 years ago

Describe the bug In my application, based on the user property, I need to change the request.user to a different user.

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

  def __call__(self, request):
     print(request.user)

     response = self.get_response(request)
     return response

and the MIDDLEWARE stack is

MIDDLEWARE = [
    'django_hosts.middleware.HostsRequestMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',

    'whitenoise.middleware.WhiteNoiseMiddleware',

    'debug_toolbar.middleware.DebugToolbarMiddleware',
    'silk.middleware.SilkyMiddleware',
    'django_hosts.middleware.HostsResponseMiddleware',

    'oauth2_provider.middleware.OAuth2TokenMiddleware',
    'myapp.middleware.CustomMiddleware',
]

Rest Framework Configuration

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
    ]
}

The request.user in the CustomMiddleware gives AnonymousUser object even though user is authenticated. It gives user object in the views.

Same middleware is working fine when using admin panel.

Expected behavior It should return the authenticated user.

Version django-oauth-toolkit==1.3.0

Additional context

BuSHari commented 4 years ago

I'm having this issue too, it will only work if i will put it after the response but i want to get it before the response

example:

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

  def __call__(self, request):
     response = self.get_response(request)
     print(request.user)

     return response
BuSHari commented 4 years ago

It only happen when grant_type=client_credentials

BuSHari commented 4 years ago

@anuj9196 i think you forgot to put the 'oauth2_provider.middleware.OAuth2TokenMiddleware' after 'django.contrib.auth.middleware.AuthenticationMiddleware' under the MIDDLEWARE

anuj9196 commented 4 years ago

@BuSHari I have that middleware added to the list above CustomMiddleware.

I have updated the list in the first post.

Still getting anonymous user.

Authorization Grant Type is Resource owner password-based

anuj9196 commented 4 years ago

I forgot to add the authentication backend. Adding authentication backed solved the issue

AUTHENTICATION_BACKENDS = (
    'oauth2_provider.backends.OAuth2Backend',
    # Uncomment following if you want to access the admin
    #'django.contrib.auth.backends.ModelBackend'
    '...',
)
itaisir commented 1 year ago

I'm facing the same issue and after I added this code it is still not working

AUTHENTICATION_BACKENDS = (
    'oauth2_provider.backends.OAuth2Backend',
)
itaisir commented 1 year ago

@anuj9196 my Middleware order is

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

and my middleware class is


class MobileUserMiddleware(MiddlewareMixin):
    def process_request(self, request):
        if 'mobile' in request.path:
            if request.user.is_authenticated:
                if not request.user.mobileappuser:
                    return HttpResponseForbidden()
                else:
                    request.mobileappuser = request.user.mobileappuser

Although the user is authenticated inside the view it is not here authenticated while I'm adding my middleware at the end of the list, I read that DRF is processing the authentication in the view level, but that doesn't make sense !

itaisir commented 1 year ago

@anuj9196

I started to validate the user myself for now In this way, hopefully, we can check it the same way the framework is sticking with

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

    def __call__(self, request):
        if 'mobile' in request.path:
            token_key = request.META.get('HTTP_AUTHORIZATION', '').split(' ')[-1]
            if token_key:
                token = Token.objects.filter(key=token_key).first()
                if token:
                    User = get_user_model()
                    request.user = User.objects.filter(pk=token.user_id).first()
                    try:
                        request.mobileappuser = request.user.mobileappuser
                    except Exception:
                        return HttpResponseForbidden()                        
        response = self.get_response(request)
        return response
emyller commented 8 months ago

Looking at the authentication backends has shed the light we needed.

In our case, with a combination of django-restframework and django-polymorphic, we wanted to replace request.user with an instance of the user as its polymorphic type, e.g. AdminUser instead of User. Here's how we managed it:

# auth_backends.py
from rest_framework.authentication import TokenAuthentication

from ...models.user import User

class PolymorphicTokenAuthentication(TokenAuthentication):
    """
    Token authentication that supports polymorphic users
    """

    def authenticate_credentials(self, key):
        user, token = super().authenticate_credentials(key)
        if isinstance(user, User):
            user = user.get_real_instance()
        return user, token
# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'myapp.accounts.api.v2.auth_backends.PolymorphicTokenAuthentication',
    ],
    ...
}

I hope this helps creating ideas.