cal5k
cal5k

Reputation: 21

In django-rest-framework, is it possible to use oauth and session authentication simultaneously?

Here's what I'm trying to do: I'm building a finance app. There's a web component, which is using Django templates and jQuery for AJAX requests, and a mobile client.

My goal is to use session-based authentication for the AJAX requests coming from the web app, using django-allauth for authentication, and OAUTH2 for the mobile app with django-oauth-toolkit. I'm using django-rest-framework for the endpoints.

The AJAX behaviour was working fine before I added the OAUTH middleware/authentication backend. This code from my view.py now prompts a 401 unauthorized when accessed via AJAX call, even when the user is authenticated using django-allauth. It worked previously (and still works when accessed via curl with an access token):

@api_view(['GET'])
def portfolio(request):
    """
    Get account balances, total portfolio value in CAD, and balances converted to CAD at current market rates.
    """
    try:
        account = request.user.account
    except ObjectDoesNotExist:
        return Response(status=Status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = AccountSerializer(account)
        return Response(serializer.data)

Here are the relevant bits of mysettings.py:

MIDDLEWARE = [
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'oauth2_provider.middleware.OAuth2TokenMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django_otp.middleware.OTPMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

AUTHENTICATION_BACKENDS = (
    'allauth.account.auth_backends.AuthenticationBackend',
    'django.contrib.auth.backends.ModelBackend',
    'oauth2_provider.backends.OAuth2Backend',
)

An older method I wrote that doesn't use django-rest still works fine:

@verified_account_required
def get_rates(request):
    if request.method == 'POST':
        buy_rates,sell_rates = utility_rates()

        if request.POST.get('action') == 'buy':
            data = buy_rates
        else:
            data = sell_rates

        return JsonResponse(data)

How can I use Django-rest with both authentication types simultaneously? Or is that not possible?

I am, admittedly, quite new to Django - so there is probably something I am just not understanding correctly here.

Upvotes: 2

Views: 1531

Answers (2)

user987055
user987055

Reputation: 1149

Yes, it is possible...

The solution is in the order of the validators in the authentication_classes, which depending on the order, the request.user was being removed in the dispatch and this meant that the session authentication did not work when the oaut authentication was first; the configuration that I make is the following, in the view:

class UserViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, 
              mixins.UpdateModelMixin, mixins.DestroyModelMixin, 
              viewsets.GenericViewSet):

queryset = User.objects.all()
serializer_class = UserModelSerializer

authentication_classes = [SessionAuthentication, OAuth2Authentication]
permission_classes = [IsAuthenticatedOrTokenHasScope]

the request is being made as follows:

cookies = {
    'sessionid': request.session.session_key
}
    
headers = {
    'remote-user': request.user.get_username(),
    'authorization': "{} {}".format(
        'Bearer', request.user.oauth_access_token)
}
    
url = 'http://user-ms:8084/api/v1/user/'
request_data = requests.get(url=url, cookies=cookies, headers=headers)

additionally, if you want it to work with the two separate authentication types, session only or oauth only, you must install the rest_framework.authtoken app and migrate

Upvotes: 0

harrouet
harrouet

Reputation: 177

I am using class-based views with this set-up, with one difference in my settings:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
    ),
}

then in the API view, I have the following:

from rest_framework import viewsets, permissions, authentication
from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope, OAuth2Authentication
from . import models, serializers

class MyViewSet(viewsets.ModelViewSet):
    serializer_class = serializers.MySerializer
    authentication_classes = [OAuth2Authentication, authentication.SessionAuthentication]
    permission_classes = [IsAuthenticatedOrTokenHasScope,]
    required_scopes = ['scope',]

It all works fine.

But true, adding 'oauth2_provider.contrib.rest_framework.OAuth2Authentication' in the DEFAULT_AUTHENTICATION_CLASSES of the DRF settings did not work. It may be that the OAuth2 authentication backend throws a 401, since it does not find a Token in the AJAX request, and that it is not handled by DRF to give SessionAuthentication a second chance.

I hope this gives you hints to use OAuth2 in your function-based views.

Upvotes: 1

Related Questions