DRF - extend obtain auth token - django

I have Django Rest Framework with token auth. I have a following url url(r'^api/auth/', views.obtain_auth_token), which returns me token.
What I need to do is perform some db logic, when user performs authorization which is getting the token. I need to query db and do some stuff there.
It seems to me that I have somehow to override default behaviour and add some custom logic to obtain_auth_token.
How can I do that ?

ObtainAuthToken from Rest Framework gets or creates a token for an specific user and then sends it in the Response, all of these behaviour is done in the post method.
The documentation says:
If you need a customized version of the obtain_auth_token view, you can do so by overriding the ObtainAuthToken view class, and using that in your url conf instead.
So you can override the post method, or even create your own APIView to create the Token and add the behaviour you want. In order to do that, change your url:
url(r'^api/auth/', views.custom_obtain_token)
And in views.py:
class CustomObtainToken(APIView):
...
def post(self, request):
<your logic>
<get token n your own way or using DRF way>
return Response({'token': token})
custom_obtain_token = CustomObtainToken.as_view()

Related

How can I pass in a CSRF token with my API requests if I don't have a user login for my Django site?

Using the Django rest framework, I have an API endpoint I created that I call within my static files that I would like to pass a CSRF token into so that I'm the only one who can access the API.
My Django site does not have users with logins.
I essentially want to do something like this in my API endpoint:
#api_view(['POST'])
def payment(request, *args, **kwargs):
if (("Authorization" not in requests.headers) or (request.headers["Authorization"] != "token")):
return Response({"Error": "Not authorized for access."})
# ...
Do I need to generate a token one time and use that repeatedly?
Or can I generate one every time the script is used?
And how can I access this csrf token in my HTML file?
I'm using class-based views and I assume I would pass it in to get_context_data, but how would I set up the API endpoint to accept this CSRF token?
I would suggest to use TokenAuthentication right from the django-restframework:
https://www.django-rest-framework.org/api-guide/authentication/
You would then pass the token in the header of all your requests
like Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
If you use the decorators from the django-restframework, you can skipp all the custom token validation, as this is done by the django-restframework.
#api_view(['POST'])
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
def payment(request, *args, **kwargs):
# ...

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!"})

csrf_exempt set but CSRF Failed: Referer checking failed - no Referer

I have a backend API, it's in django and deployed on Google Endpoint.
I have a post request that insert data to my DB.
I created a script to use this endpoint but I got this error:
{"detail":"CSRF Failed: Referer checking failed - no Referer."}
Regarding over posts I added the crsf_exempt decorator to my class but it did not change.
I try to add the decorator two ways:
class AddUser(APIView):
""" Create user and company from csv """
#method_decorator(csrf_exempt)
def post(self, request):
#method_decorator(csrf_exempt, name='dispatch')
class AddUser(APIView):
""" Create user and company from csv """
def post(self, request):
But both failed.
This is how I contact my endpoint:
resp = requests.request(
method, url,
headers={'Authorization': 'Bearer {}'.format(
open_id_connect_token)}, **kwargs)
Any ideas ?
Thanks
EDIT
So I tried to add authentication classes to my views but it appears to be a bad idea. This is being real trouble for me.
I tried to get the csrftoken doing like this:
client = requests.session()
# Retrieve the CSRF token first
client.get(url) # sets cookie
print(client.cookies)
if 'csrftoken' in client.cookies:
# Django 1.6 and up
csrftoken = client.cookies['csrftoken']
else:
# older versions
csrftoken = client.cookies
Thing is, I am using IAP to protect my API and I do not have any csrftoken cookie but I do have a something looking like this:
<RequestsCookieJar[<Cookie GCP_IAP_XSRF_NONCE_Q0sNuY-M83380ypJogZscg=1
for ...
How can I use this to make post request to my API ?
So this happened to me because I did not set any authentication_classes to my generic view.
When this option is not set Django automatically use the SessionBackend, which need the csrf token.
I fixed it by adding this to my view: authentication_classes = [ModelBackend, GoogleOAuth2]
#Kimor - Can you try doing this in your urls.py
from django.views.decorators.csrf import csrf_exempt
url('^test/$', csrf_exempt(views.TestView.as_view())),
The get and post methods defined on the APIView class just tell DRF how the actual view should behave, but the view method that the Django router expects is not actually instantiated until you call TestView.as_view().
source
Django REST Framework CSRF Failed: CSRF cookie not set
So after working on this project for a while this is what I learned regarding the CSRF using Django.
First of all, if you are using django templates, or in any cases where your back-end and front-end are running behind the same server the most common practice is to use session for authentication.
This is activated by default by DRF.
This means that in your DRF configuration if you do not explicitly set the DEFAULT_AUTHENTICATION_CLASSES option default authentication will be set to Session + BasicAuth.
In this configuration you'll need to manage the CSRF token as described in the documentation (https://docs.djangoproject.com/en/4.0/ref/csrf/).
If your back-end and front-end are separated as in my case, using CSRF is not the only solution or even the recommended one.
As in my case I use JWT behind IAP (Identity Aware Proxy, provided by google). I had to write my own authentication classes and then use it in my DRF conf:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'main.authentication_backend.custom_auth.IAPAuthentication'],
...
}
Here is explain how to write your own authentication class: https://www.django-rest-framework.org/api-guide/authentication/#custom-authentication.

Django: reencode request and call another view

I am working on a backend application that exposes a JSON-based REST api. However, I am using some library that has some of its own endpoints and they accept form-endcoded data. I want to extend the actions done by those endpoints and expose my extended versions. At the same time, I want my API to be consistent and I want all my endpoints to use JSON. To be more specific, I use the oauth2_provider library, and I want to logout users when they revoke a token. I am also considering making a logout handle that would require the bearer token in the Authorization header and would both logout the user and revoke the token.
My first approach to this was to write a wrapper view around the oauth2_toolkit revoke token view, loggging the user out in the wrapper view and then calling the actual revoke_token view. However, I have to modify the body of the request, which is immutable.
class Logout(View):
def get(self, request):
if request.user.is_authenticated:
logout(request)
# modify the .body attr of the request or create a new request here
RevokeTokenView.as_view(request)
I couldn't find a way to clone a Django request or modify it. Is there a way to do it?
(For now I am looking into creating a custom oauthlib_backend_class, but it feels as a bit of an overkill)
UPD: the data that is required by the revoke_token view is in the request_body
You can modify the immutable request body like below:
class Logout(View):
def get(self, request):
if request.user.is_authenticated:
logout(request)
# modify or create a new request here
body = request.GET.copy()
body['key'] = 'new_value'
request.GET = body
RevokeTokenView.as_view(request)
UPDATE: If you want to change .body attribute of the request object, according to django's implementation, you should change the ._body attribute which is a private attribute of the request object.

How to include user specific access to Django REST API in this example?

My understanding of authentication via an API is that the HTTP request sent by the client needs to include credentials, whether that be just a raw username and password (probably bad practice) or a hashed password, token, etc.
Normally in my Django views, I just use:
request.user.is_authenticated():
If I want my API to be used with an iOS app, this line of code cannot be used because it relies on sessions/cookies?
I would like to edit the following function, to allow it access to a specific user:
api_view(['GET'])
#csrf_exempt
def UserInfoAPI(request):
###if HTTP header includes name and password:###
private_info = Entry.objects.filter(user=request.user)
serializer = EntrySerializer(private_info, many=True)
return Response(serializer.data)
Is there a simple way to manually check for a username/pass in the HTTP header? I don't actually plan to use this in a production environment, but for the sake of understanding, I would like to understand how to have this function verify a username/pass from the http header.
Django REST Framework tries to determine the user that sends the request looking into the Authorization HTTP header. What you should send inside this header depends on the authentication scheme you choose. For example, if you choose BasicAuthentication, your header would be:
Authorization: Basic <"user:password" encoded in base64>
or, if you choose TokenAuthentication:
Authorization: Token <your token>
I would recommend the TokenAuthentication scheme. More schemes are listed in the docs.
To make sure only authenticated users have access to that API's endpoint, use the IsAuthenticated permission. This will check your user's credentials in the request, and if they are not correct, it will raise a HTTP 401 Unauthorized error.
Your Django REST Framework view would look something like this:
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
class UserInfo(generics.ListAPIView):
model = Entry
serializer_class = EntrySerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
queryset = super(UserInfo, self).get_queryset()
user = self.request.user
return queryset.filter(user=user)
As for the code in your iOS app, this post may be helpful.