Django Rest Framework Custom authentication + Basic Auth - django

I am using the Django Rest Framework in my Python-Django app, and am using a Custom authentication for the api.
If I work just with my custom authentication method, works correctly.
#authentication_classes((CustomAuthentication, ))
But If I try to have basic authentication and my custom authentication, in that order, my custom authentication never executes. I mean, I want that if the Basic authentication fails, then try with the custom authentication. The Basic Authentication is executes and then ends.
#authentication_classes((SessionAuthentication, BasicAuthentication, CustomAuthentication ))
Is possible to have at the same time this three authentication methods, and execute them in that order?

#Arpit Goyal's answer makes the workflow clear.
If you INDEED want to go through all the authentication classes,
here's a work around solution you can try. I hope it can help you.
#authentication_classes((AuthencationWrapper, ))
add a AuthencationWrapper
class AuthencationWrapper(BaseAuthentication):
authenication_classes = (
BasicAuthentication,
SessionAuthentication,
CustomAuthentication,
)
def authenticate(self, request):
exc = None
ret = None
for auth in self.authentication_classes:
try:
ret = auth().authenticate(request)
# if success, we will break, else we will continue to call next one
break
except exceptions.AuthenticationFailed as e:
# we only record the first failed exception
if exc is None:
exc = e
self.first_failed_auth = auth()
if ret is None:
raise exc
# one of the authentication_classes is passed
return ret
def authenticate_header(self, request):
# actualy this way breaks what django-rest-framework doing now
return self.first_failed_auth.authenticate_header(request)
# the one follows what django-rest-framework doing now
# choose the first authentication class header
## if self.authentication_classes:
## return self.authentication_classes[0].authenticate_header(request)

The Django Rest Framework authentication documentation clearly states this:
The authentication schemes are always defined as a list of classes.
REST framework will attempt to authenticate with each class in the
list, and will set request.user and request.auth using the return
value of the first class that successfully authenticates.
If no class authenticates, request.user will be set to an instance of
django.contrib.auth.models.AnonymousUser, and request.auth will be set
to None.
So whenever your first class authenticates request.user and request.auth is set.
In case you want to authenticate with your CustomAuthentication class return None for BasicAuthentication so that all your authentication classes are used but the user is set as per your CustomAuthentication

Related

Django Rest Framework permissions outside Rest Framework view

I am using Rest Framework Token authentication. Which means I cannot know if a user is authenticated outside a rest framework view eg:(A regular django view). The Rest Framework token authentication is a custom auth system which can only be used in a rest framework view.
In a normal rest framework view, I can restrict the endpoint for authenticated users by using this:
class ExampleView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)
But how will I do that for a regular django view. eg:
def someDjangoView(request):
'''
Note that I cannout use request.user.is_authenticated.
It will always return false as I am using rest framework token authentication.
Which means the request parameter should be of rest framework's and not django's built-in.
'''
content = {"detail": "Only authenticated users should access this"}
return JsonResponse(content)
I am stuck in a situation where I have to know if a user is authenticated (custom auth) outside a rest framework view.
Is there any way to do that?
You can use the api_view decorator to your function-based view to enable DRF:
from rest_framework.decorators import api_view, authentication_classes
#api_view(http_method_names=['GET', 'POST'])
#authentication_classes([YourTokenAuthenticationClass])
def someDjangoView(request):
print(request.user)
...
return JsonResponse(content)
DRF builds on top of the builtin Django contrib.auth user auth system. So, for regular django views, you can use the regular methods provided by contrib.auth.
DRF also supports session-based authentication (usually the default when using contrib.auth). This is ideal, for example, when you have some JavaScript code running in the browser with the user's session.
Note that I cannout use request.user.is_authenticated.
It will always return false as I am using rest framework token authentication
If you are using rest framework token authentication, then you must use views that are compatible with that. request.user.is_authenticated is part of the contrib.auth system built into django. However, you must authenticate a user for this to be True. Rest Framework does this for you. If you're not using the rest framework, you must auth the user yourself!
A simple answer may be to decorate your views to make them utilize the rest framework authentication you define:
#api_view(['GET'])
#authentication_classes(...) # if defaults are not applied
#permission_classes(...) # to apply permissions you need
def view(request):
return Response({"message": "Hello for today! See you tomorrow!"})

Django-rest-framework and django-rest-framework-jwt APIViews and validation Authorization headers

I'm using DRF and DRF-jwt to secure my APIs. Currently I have some CBV written like this
class Organization(APIView):
permission_classes = (IsAuthenticated,)
#method_decorator(csrf_exempt, name='dispatch')
class OfficeVisitsOverview(APIView):
def post(self, request, *args, **kwargs):
cursor = connection.cursor()
(before, today) = getDateRange()
cursor.execute("SELECT format(COUNT(*), 'N0') \
FROM Medi_OfficeVisit \
WHERE ( cast(VisitDate as date) BETWEEN '{0}' AND '{1}' ) \
".format(before, today))
data = dictfetchall(cursor)
connection.close()
return JsonResponse({"numberOfOVs": data[0][""]})
From my understanding the APIView and the permission class IsAuthenticated makes sure that theres an Authorization token being sent with the request header. How can you be sure that no one has modified the JWT? How do i know that the Secret_Token in my Django app is being used every time to decode/encode/verify/validate the JWT that is being received/sent with every request? Is this enough security for my APIs to be opened to the public?
Is authenticated just makes sure that current request.user.is_authenticated is True. It is authentication backend's responsibility to check for headers, validate tokens and so on and set User.is_authenticated. This is one you have added in your settings file while setting up rest-framework-jwt. This is an application created exactly for purpose of secure authentication, so yes, it's enough. But you still have to take care of other aspects such as SSL, sql injection and so on (search for Django security).
Warning! Do not use .format to create SQL queries, as this is direct way for SQL injection. If you later use some user provided parameters for your query, you will be in danger. Pass parameters as second argument to cursor.execute or use ORM to avoid this.

How to return a custom response object from the Django rest framework's custom Authentication class

I have scrached my head over this for almost 3-4 days.
Let me explain the situation. I have a DRF(Django REST Framework) class based view with a custom authentication class. As far as I understand, you can override the authenticate method of DRF's BaseAuthentication class to implement your custom authentication, while you can only raise predefined Exceptions provided from DRF if the authentication fails.
My problem is, I am trying to find a way to return custom response i.e; the captcha HTML to the frontend directly from the authentication class, so as to achieve no authentication related code in my view.
To have a better understanding of my situation I am providing a pseudo code below.
class ExampleView(APIView):
authentication_classes = (ExampleCustomAuth, )
def get(self, request):
pass
This is the view and this part is absolutely fine.
class ExampleCustomAuth(BaseAuthentication):
def authenticate(self, request):
req = request
request = req._request
{
This part of code decides if its required for a
captcha or not
}
if captcha_required:
response = HttpResponse()
response.status_code = 401
response['WWW-Authenticate'] = 'Captcha" id="%s"'% (id)
response.content = loader.render_to_string('captcha.html')
return response # This is where it goes wrong
I believe, its not possible to return a response right from here.
I hope someone has figured a way around this.
Thank you in advance !
Well I finally figured a way to get it working.
According to the DRF docs, the authenticate method should be overridden for any authentication logic and also have to override the authenticate_header, so that if you raise an exception in authenticate method, you can return a string from the authenticate_header method, which will be used as a value for the www-Authenticate header.
Below is how the implementation works.
class ExampleCustomAuth(BaseAuthentication):
def authenticate(self, request):
req = request
request = req._request
{
This part of code decides if its required for a
captcha or not
}
if captcha_required:
raise exceptions.AuthenticationFailed(loader.render_to_string('captcha.html'))
def authenticate_header(self, request):
return 'Captcha" id="%s"'% (id)

django-rest-auth: social login with google

The django-rest-auth documentation discusses Facebook integration, which I am not interested in-- my concern is providing social login via Google. I have tried this for quite some time now and I'm wondering if anyone else has any documentation on how they did this...even just a rough sketch would be helpful. So far, I have not found any results for this search. I am almost there, but cannot get it to work with the Django rest framework (DRF) browsable API.
Here is what I have so far:
I started from the demo project supplied on the django-rest-auth github page and modified the social login template HTML page to only require the 'code' input, not both 'code' AND 'access_token'. When I supply a valid code (obtained by a separate request to google's auth endpoint), this works fine; the browsable API renders the usual web page with the 'key' (my application's API token for the user) in the response. Checking the django admin, everything worked- the user is logged in, email is authenticated, etc. Good so far.
The issue is that starting point of supplying the 'code'- and how I get that code from google in the first place. When I previously (successfully) used the allauth package, I could simply click on a link, which would "invisibly" perform the whole OAuth2 flow (i.e. request the code, use that code to get the access token, and use the access token to get user's google account info).
To recreate that seamless flow (i.e. NOT starting out with the code), I figured I could interrupt the OAuth2 flow and "intercept" the code returned from google, and then POST that code to the rest-auth social login API. To that end, I created a custom allauth.socialaccount.providers.oauth2.views.OAuth2CallbackView by overriding the dispatch method:
class CustomOAuth2CallbackView(OAuth2CallbackView):
def dispatch(self, request):
# gets the code correctly:
code = request.GET['code']
# rp is of type requests.methods.Response
rp = requests.post(<REST-AUTH API ENDPOINT>, data = {'code':code})
return rp
Usually, this method is called when google sends a GET request to the callback uri you initially supply to google's auth endpoint. With this override, I am able to successfully parse the code returned from google in that callback. The POST request works and has the user's key in the resp._content field. However, it ultimately fails to produce the intended view in the DRF browsable API.
Based on diving down in the callstack, I find that rest_framework.views.APIView.dispatch returns an object of type rest_framework.response.Response. However, when the requests.post method used above completes, it returns an instance of type requests.models.Response. As a result, it does not have the proper attributes and fails in the django middleware. For example, it has no acceptable_renderer attribute and no 'get' method (which is used in django.middleware.clickjacking.py). I could, conceivably, add these requirements to the requests.models.Response (rp) instance, but then this hack becomes even more of a kludge.
Thanks for any help you can provide!
https://github.com/st4lk/django-rest-social-auth
class SocialLoginSignup(SocialSessionAuthView):
"""
Mobile user social signup and login api view
args:
provider: name of the social network
access_token: auth token got from the social sites
"""
serializer_class = SocialSignUpSerializer
authentication_classes = (TokenAuthentication,)
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
provider_name = serializer.validated_data['provider']
decorate_request(request, provider_name) # assign the provider class object in request
authed_user = request.user if not request.user.is_anonymous() else None
token = serializer.validated_data['access_token']
if self.oauth_v1() and request.backend.OAUTH_TOKEN_PARAMETER_NAME not in serializer.validated_data:
request_token = parse_qs(request.backend.set_unauthorized_token())
return Response(request_token)
try:
# authentication function get call from assign class object in request
user = request.backend.do_auth(token, user=authed_user)
except social_exceptions.AuthException as e:
raise exceptions.ParseError({'error':str(e)})
except social_exceptions.AuthTokenError as e:
raise exceptions.ParseError({'error': str(e)})
except social_exceptions.AuthAlreadyAssociated as e:
raise exceptions.ParseError({'error': str(e)})
except social_exceptions.AuthFailed as e:
raise exceptions.ParseError({'error':str(e)})
except social_exceptions.AuthUnknownError as e:
raise exceptions.ParseError({'error': str(e)})
except social_exceptions.WrongBackend as e:
raise exceptions.ParseError({'error':str(e)})
except Exception as e:
raise exceptions.ParseError({'error': social_message.INVALID_AUTH_TOKEN})
token, created = Token.objects.get_or_create(user=user)
return Response({'auth_token':token.key})

Looking for a comprehensive guide to setting up custom authentication backends in Django, or pointers

I'm trying to set up a custom backend that queries another database, for which I have created a model in the system. It uses its own rules (email instead of username, and a differently salted/hashed password) so I can't use built in authentication. I've set up a custom authentication backend like so:
class BlahBlahBackend:
def check_password():
# check password code here
return true
def authenticate(self, email=None, password=None):
import myapp.models.loginmodel
try:
person = myapp.models.loginmodel.People.objects.get(email=email)
if check_password(password, person.password):
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
username=person.first_name + person.last_name
name_count = User.objects.filter(username__startswith = username).count()
if name_count:
username = '%s%s'%(username, name_count + 1)
user = User.objects.create_user(username,email)
else:
user = User.objects.create_user(username,email)
except People.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
I've added BlahBlahBackend as an authentication backend:
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',
'socialauth.auth_backends.OpenIdBackend',
'socialauth.auth_backends.TwitterBackend',
'socialauth.auth_backends.FacebookBackend',
'socialauth.auth_backends.BlahBlahBackend',
)
As you can see, I'm also using some pre-existing auth backends that are also in socialauth.
I have a submission form that points to the following view:
def blahblah_login_complete(request):
email = request.POST.get('email')
password = request.POST.get('password')
user = authenticate(email,password)
# if user is authenticated then login user
if user:
login(request, user)
else:
return HttpResponseRedirect(reverse('socialauth_login_page'))
However, when I try to login in this way, it seems like one or more of the other backends are acting as if I'm trying to log in using their method.
I read that backends are cached and so ran
Session.objects.all().delete()
to clear out the backends cache.
My main questions are:
Does the order in which items are listed in AUTHENTICATION_BACKENDS
How does the system decide/know which Backend to use? This was never made clear by any of the documentation, and I find it a bit confusing.
Is there any way to force the use of a specific authorization based on the request. In other words, if someone submits a form, is there a way to force them to use the form-login-based authentication as opposed to the login via openid or Twitter?
Update:
It works! This is very cool, thanks. I guess it just seemed like the django doc was saying "You don't have to do anything else, it just sort of works like magic" and it turns out this is absolutely the case. So long as the backend is there and the credentials are set up correctly, the authentication will work. As it turns out the real problem was a misconfiguration in the urls.py file that wasn't sending the post from the login form to the correct handler, which is why it kept trying to use another authentication method.
You're supposed to use keyword arguments to django.contrib.auth.authenticate() The names should match the names of the arguments in your backend's authenticate method. The default backend handles the names 'username' & 'password'.
Your backend can use a different name for the keyword arguments e.g.: blahblah_email and blahblah_password, and then call authenticate(blahblah_email=..., blahblah_password=...).
It's clearly described here -
django tries each backend in order
defined, if first fails to
authenticate it goes to second etc.
I believe you can load backend class dynamically and authenticate
directly through it. Look at django authenticate() function sources on how to do that.
I guess django-cas will be a good reference for you :)
And yes, the order of AUTHENTICATION_BACKENDS matters.
Django loops over the backends list and stop at the first backend that has a authenticate method accepting the credential parameters you passed to it.