I am using Django REST Framework with simple JWT, and I don't like how permission classes generate a response without giving me the final say on what gets sent to the client. With other class-based views not restricting permissions (login, registration, etc.), I have control over how I handle exceptions, and I can choose how the response data is structured.
However, anytime I introduce permission classes, undesired behavior occurs. My desired structure is best represented in my LoginView (see try/except block):
NON_FIELD_ERRORS_KEY = settings.REST_FRAMEWORK['NON_FIELD_ERRORS_KEY']
class LoginView(GenericAPIView):
"""
View for taking in an existing user's credentials and authorizing them if valid or denying access if invalid.
"""
serializer_class = LoginSerializer
def post(self, request):
"""
POST method for taking a token from a query string, checking if it is valid, and logging in the user if valid, or returning an error response if invalid.
"""
serializer = self.serializer_class(data=request.data)
try:
serializer.is_valid(raise_exception=True)
except AuthenticationFailed as e:
return Response({NON_FIELD_ERRORS_KEY: [e.detail]}, status=e.status_code)
return Response(serializer.data, status=status.HTTP_200_OK)
However, what happens when I try to define a view using a permission class?
class LogoutView(GenericAPIView):
"""
View for taking in a user's access token and logging them out.
"""
serializer_class = LogoutSerializer
permission_classes = [IsAuthenticated]
def post(self, request):
"""
POST method for taking a token from a request body, checking if it is valid, and logging out the user if valid, or returning an error response if invalid.
"""
access = request.META.get('HTTP_AUTHORIZATION', '')
serializer = self.serializer_class(data={'access': access})
try:
serializer.is_valid(raise_exception=True)
except AuthenticationFailed as e:
return Response({NON_FIELD_ERRORS_KEY: [e.detail]}, status=e.status_code)
return Response(serializer.save(), status=status.HTTP_205_RESET_CONTENT)
When I go to test this, requests with an invalid Authorization header are handled outside the scope of post()
, so execution never reaches the method at all. Instead, I am forced to deal with a response that is inconsistent with the rest of my project. Here's an example:
# Desired output
{
'errors': [
ErrorDetail(string='Given token not valid for any token type', code='token_not_valid')
]
}
# Actual output
{
'detail': ErrorDetail(string='Given token not valid for any token type', code='token_not_valid'),
'code': ErrorDetail(string='token_not_valid', code='token_not_valid'),
'messages': [
{
'token_class': ErrorDetail(string='AccessToken', code='token_not_valid'),
'token_type': ErrorDetail(string='access', code='token_not_valid'),
'message': ErrorDetail(string='Token is invalid or expired', code='token_not_valid')
}
]
}
Is there a simple way to change how these responses are formatted?