Reputation: 583
My code is having two mixins, BasicAuthMixin and JWTAuthMixin as mentioned below. Just assume that self.authenticate method returns True and doesn't raise any exception:
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View
class BasicAuthMixin(View):
"""
Add this mixin to the views where Basic Auth is required.
"""
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
try:
self.authenticate(request)
except:
return JsonResponse({'status': 403, 'message': 'Forbidden'}, status=403, content_type='application/json')
return super(BasicAuthMixin, self).dispatch(request, *args, **kwargs)
class JWTAuthMixin(View):
"""
Add this mixin to the views where JWT based authentication is required.
"""
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
try:
self.authenticate(request)
except:
return JsonResponse({'status': 403, 'message': 'Forbidden'}, status=403, content_type='application/json')
return super(JWTAuthMixin, self).dispatch(request, *args, **kwargs)
These mixins are being used in the views based upon the authentication needed.
The actual problem begins from here: I'm trying to create another mixin AllAuthMixin which when included in any view will automatically determine which mixins need to be called based upon the Authentication Header provided:
class AllAuthMixin(View):
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
auth = request.META.get('HTTP_AUTHORIZATION') or ''
if auth.startswith('Bearer'):
return JWTAuthMixin.as_view()(request, *args, **kwargs)
elif auth.startswith('Basic'):
return BasicAuthMixin.as_view()(request, *args, **kwargs)
raise Exception('Unauthorized Access to Saurav APIs', 403)
Once I include AllAuthMixin in any of the view say /test it actually calls the appropriate Mixins but returns Method Not Allowed (GET): /test
I debugged and found that Method Not Allowed error message is coming from below line if I use basic auth:
return super(BasicAuthMixin, self).dispatch(request, *args, **kwargs)
Following illustrates a very simple example to call my view with basic auth:
>>> import requests
>>> requests.get('http://127.0.0.1:8000/test', auth=('UserName', 'Password'))
<Response [405]>
I'm not sure what I'm doing wrong here. Can anyone please help me figure it out the issue or any alternate way to achieve this. What I want is to re-use already declared mixins: BasicAuthMixn and JWTAuthMixin.
Upvotes: 3
Views: 999
Reputation: 1770
There's a design issue here, both mixins are implemented intercepting the dispatch
method and calling super
. The way you're implementing AllAuthMixin
by also calling dispatch
means you need to have them both in its MRO and "trick" super
into choosing the appropriate one which is not a good idea.
An alternative way of implementing AllAuthMixin
is to not call dispatch
but instantiate and call authenticate
on them:
class AllAuthMixin(View):
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
auth = request.META.get('HTTP_AUTHORIZATION') or ''
try:
if auth.startswith('Bearer'):
JWTAuthMixin().authenticate(request) # raises on failed auth
elif auth.startswith('Basic'):
BasicAuthMixin().authenticate(request)
except:
raise Exception('Unauthorized Access to Saurav APIs', 403)
return super(AllAuthMixin, self).dispatch(request, *args, **kwargs)
A nicer way to reuse code would be to separate the authentication into its own class and make individual mixins that make use of them. That way you'd have better separation of concerns.
Something like:
class BasicAuth(object):
def authenticate(self, request):
# raise if not authed
print("Basic auth")
class JWTAuth(object):
def authenticate(self, request):
# raise if not authed
print("JWT auth")
class AuthMixin(View):
def authenticate(self, request):
raise NotImplementedError('Implement in subclass')
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
try:
self.authenticate(request)
except:
return JsonResponse({'status': 403, 'message': 'Forbidden'}, status=403)
return super(AuthMixin, self).dispatch(request, *args, **kwargs)
class BasicAuthMixin(BasicAuth, AuthMixin):
pass
class JWTAuthMixin(JWTAuth, AuthMixin):
pass
class AllAuthMixin(AuthMixin):
def authenticate(self, request):
auth = request.META.get('HTTP_AUTHORIZATION') or ''
try:
if auth.startswith('Bearer'):
return JWTAuth().authenticate(request)
elif auth.startswith('Basic'):
return BasicAuth().authenticate(request)
except:
return JsonResponse({'status': 403, 'message': 'Other'}, status=403)
class SomeView(AllAuthMixin, View):
def get(self, request):
return JsonResponse({'status': 200, 'message': 'OK'})
-- Original answer --
You're calling as_view
for each mixin in AllAuthMixin
, by calling as_view()(request, *args, *kwargs)
you're forcing the mixin to respond to the request but since it doesn't have a get
method it returns 405 Method not allowed as described in the docs.
You should be calling dispatch
and also make AllAuthMixin
inherit from both child mixins to properly pass self
to dispatch
. Like so:
class AllAuthMixin(JWTAuthMixin, BasicAuthMixin):
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
auth = request.META.get('HTTP_AUTHORIZATION') or ''
if auth.startswith('Bearer'):
return JWTAuthMixin.dispatch(self, request, *args, **kwargs)
elif auth.startswith('Basic'):
return BasicAuthMixin.dispatch(self, request, *args, **kwargs)
raise Exception('Unauthorized Access to Saurav APIs', 403)
class SomeView(AllAuthMixin, View):
def get(self, request):
return JsonResponse({'status': 200, 'message': 'OK'})
Upvotes: 2