DRF responses me by 403 error when I try to request as a client [Client Credential grant]

Question:

In settings.py file I have written this settings:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
}

When I call any API with a token from a password grant application, then It’s working fine, but when I try to call the same APIs with a token from a client credential grant application, it doesn’t work and it responses with 403 error:

{ "detail": "You do not have permission to perform this action." }. 

Is it because of default permission? I don’t know what permission do I have to use instead!?

Asked By: Hamidreza

||

Answers:

you need to enable tokenAuthentication and run migration to apply changes in auth table DB

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',  # <-- And here
    ],
}

INSTALLED_APPS = [
    ...
    'rest_framework.authtoken'
]

Here is the perfect blog for your usecase.

https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication

https://simpleisbetterthancomplex.com/tutorial/2018/11/22/how-to-implement-token-authentication-using-django-rest-framework.html#implementing-the-token-authentication

Answered By: prashant

Finally solved! The problem was the permission I was using. In fact, the IsAuthenticated permission checks request.user which is None when you are using client credentials grant. Since there’s no permission for supporting clien credentials grant in DRF, you must use your own DRF custom permission. This is what I needed and used:

from rest_framework.permissions import BasePermission
class IsAuthenticatedOrClientCredentialPermission(BasePermission):
    def has_permission(self, request, view):
        if request.auth is None:
            return False
        grant_type = request.auth.application.get_authorization_grant_type_display()
        if request.user is None:
            if grant_type == 'Client credentials':
                request.user = request.auth.application.user  # <-- this is because I needed to get the user either the grant is 'password' or 'client credentials'
                return True
            else:
                return False
        else:
            return True

But you may want to have a permission just for checking if the grant type is client credentials and give the permission, if so, this is what you need:

from rest_framework.permissions import BasePermission
class ClientCredentialPermission(BasePermission):
    def has_permission(self, request, view):
        if request.auth is None:
            return False
        grant_type = request.auth.application.get_authorization_grant_type_display()
        if request.user is None and grant_type == 'Client credentials':
            return True
        else:
            return False

Note: if you want to use the second custom permission, be aware that the request.user is None and you can get the owner of the client (that is sending request to you) via request.auth.application.user.

Using (custom) permissions:

You can use your custom permission by adding them to proper views. (Just like using any DRF permissions under rest_framework.permissions)

class-based views:

class ExampleView(APIView):
    permission_classes = [ClientCredentialPermission]  # <-- Add your permissions to this list

    def get(self, request, format=None):
        content = {
            'status': 'request was permitted'
        }
        return Response(content)

function-based views:

@api_view(['GET'])
@permission_classes([ClientCredentialPermission])  # <-- Add your permissions to this list
def example_view(request, format=None):
    content = {
        'status': 'request was permitted'
    }
    return Response(content)
Answered By: Hamidreza

I had the same problem. The issue in my case was the @authentication_classes that was originally enabled for when I was using credentials directly (not token). I removed them (see them below commented out). Idea came after reading the first answer here.

This works for me as I only want token base access, and so I don’t need the other authentication classes. This is how my view decoration looks like:

@api_view(['GET'])
#@authentication_classes([SessionAuthentication, BasicAuthentication])
@permission_classes([IsAuthenticated])
def apilink(request, format=None):
      .....
Answered By: Adrian Stanica