I am building an app with Django 2.1 and I want to be able to do PATCH/DELETE requests through ajax calls. Through researching about this I found out the solution to be to deceive the browser by using a POST request, but setting the header X_METHODOVERRIDE to the desired method.
I would start doing this by creating a middleware that will take care of this. What is the best way of doing?
Please note that I don't want to use Django-REST
Code so far for making the DELETE request:
view.py
class CategoryManageView(StaffRequiredMixin, View):
model = Category
response_dict = {'status': False, 'text': '', 'data': {}}
def delete(self, request, *args, **kwargs):
cat = get_object_or_404(Category, request.POST['id'])
self.response_dict['data'] = cat
cat.delete()
self.response_dict['status'] = True
self.response_dict['text'] = 'Category deleted successfuly'
return JsonResponse(self.response_dict)
If the ajax call method is set to DELETE instead of POST or GET I get error in console:
DELETE http://127.0.0.1:8000/dashboard/admin/categories/manage 403 (Forbidden)
The error code 403 indicates that this is because of CSRF protection. As the CSRF documentation shows, PUT and DELETE - as well as POST - are considered "unsafe" methods, and Django therefore disallows the requests if they don't have a valid CSRF token.
The same page has documentation on how to enable the token in your Ajax requests. Alternatively - although this is strongly discouraged - you can use the #csrf_exempt decorator on the view to disable the protection.
Related
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.
I am trying to delete an entry using an ajax call.
when I am using the default 'delete' method from generics.DestroyAPIView, I am getting a 403 Forbidden, but if I add a post method, call the delete method immediately and change the ajax type to 'post' it works fine. Would anyone have an idea what causes this?
Note that I overwrite the get_object function to get the object based on posted data. (could it be due to delete methods not allowing to post data? If so, why? And how would you pass the CSRF token??)
ajax:
$.ajax({
url: '{% url "account:api:post_details_delete" %}',
type: 'delete',
data: { csrfmiddlewaretoken: "{{ csrf_token }}", name: json.name, value: json.value }
});
url:
path('post_details/delete/', PostDetailDeleteApiView.as_view(), name='post_details_delete'),
view:
class PostDetailDeleteApiView(generics.DestroyAPIView):
serializer_class = PostDetailSerializer
# the code below allows it to work if uncommented and type in ajax changed to 'post'
# def post(self, request, *args, **kwargs):
# return self.delete(request, *args, **kwargs)
def get_object(self):
"""
Returns the post detail object to be deleted based on the name:value pair
provided by the ajax call.
"""
data = self.request.data
obj = Post_Detail.objects.filter(
name=data.get('name', ''), value=data.get('value', '')
).filter(
post__account__user=self.request.user
)
if obj.exists():
return obj.get()
else:
raise Http404
serializer:
class PostDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Post_Detail
fields = ['name', 'value']
A 403 Forbidden error code would suggest that in some way or another that either permission is denied or that you're not authenticated.
The 'DestroyAPIView' is used for delete-only API endpoints for a single model instance.
Do you have the full traceback that you can append to your question?
Docs References
Django REST Framework's Destroy API View
Django REST Framework's Guide to Permissions
According to Django documentation you must pass the csrftoken to headers of ajax request
While the above method can be used for AJAX POST requests, it has some inconveniences: you have to remember to pass the CSRF token in as POST data with every POST request. For this reason, there is an alternative method: on each XMLHttpRequest, set a custom X-CSRFToken header (as specified by the CSRF_HEADER_NAME setting) to the value of the CSRF token. This is often easier because many JavaScript frameworks provide hooks that allow headers to be set on every request.
I'm really stuck at this problem for a couple of days now.
While I understand, what's happening here, I don't really know the best workaround/solution for this.
Problem:
I'm trying to create a user login endpoint using Django and DRF in general.
My login API needs to support a login via password as well as login via OTP.
My LoginView looks like:
def post(self, request, **kwargs):
"""
post
Method to handle user login
:param request:
:param args:
:param kwargs:
:return:
"""
request_data = request.data
login_using_password = request_data.get('login-with-password') is True
login_using_otp = request_data.get('login-with-otp') is True
if request_data is not None:
if all((login_using_password, login_using_otp)):
raise accounts_exceptions.InvalidLoginRequestError()
if login_using_password:
return Response(self._login_with_password(request))
elif login_using_otp:
return Response(self._login_with_otp(request))
raise accounts_exceptions.InvalidLoginRequestError()
return Response(self._login_with_password(request))
Also my _login_with_password looks like:
def _login_with_password(self, request, **kwargs):
"""
_login_with_password
A utility method to handle login with password
:param request:
:return:
"""
return getattr(ObtainJSONWebToken.as_view()(request=request._request, ), 'data')
When I try to login, Django complains saying RawPostDataException You cannot access body after reading from request's data stream
I'm using JWT to authenticate requests. ObtainJSONWebToken is a view provided by DRF-JWT to obtain access tokens to authenticate requests.
What is the workaround/solution for this?
Is there a better way to support such a login requirement?
Thanks in advance!
Resolved this.
There's no concrete way to solve the problem above.
Django disallows access to request.data multiple times.
It could be done only once for the entire request lifetime.
So, this left me with two solutions:
Move my request payload to query params.
Move my request payload to url context.
I ended up using a mix and match of both.
So, basically I used request.query_params and self.context to fetch data from the request and changed my URL and request structure accordingly.
Getting a csrf error I cant figure out how to fix, i have rest auth working, user is able to update their details like so:
but with Django Comments i get this csrf error using the same csrf token Error:
I would like to get rid of this error on the /comments/post/ endpoint, such that this endpoint behaves similar to /rest-auth/user/ view which accepts an "Authorization: Token 792b5fb27b4fe805e895c91274f26b6ab13cb654" header field to relevant provide data to the authenticated user.
The following is an exert of the csrf related decotaros on the respective views shown in the screen shots:
From the /comments/post/ endpoint
#csrf_protect
#require_POST
def post_comment(request, next=None, using=None):
# Fill out some initial data fields from an authenticated user, if present
data = request.POST.copy()
if request.user.is_authenticated():
if not data.get('name', ''):
data["name"] = request.user.get_full_name() or request.user.get_username()
if not data.get('email', ''):
data["email"] = request.user.email
From the /rest-auth/user/ endpoint
#api_view(['GET'])
#permission_classes((IsAuthenticated, ))
def get_user(request, **kwargs):
pk = request.data['pk']
user = MyUser.objects.get(pk=pk)
serializers = UsersSerializer(user)
return Response(serializers.data)
You're using the wrong content type. Please change it into application/json and try again.
The decorators for your endpoints are different, thus you need to adjust the headers accordingly.
For your /rest-auth/ view the WWW-Authenticate header is required as mentioned here.
The comments view /comments/ endpoint has the csrf_protect decorators which means that the header must match the csrf-token returned in the cookie,as Fede mentions in your header you only require 'X-CSRFToken' with the matching value from the cookie.
I think you are using django-rest-framework which comes with the csfr token exempt by default, but postman is sending a csfr token that is why you are getting that error.
cleaning the cookies might solve the problem.
I am using django with rest framework and I try to test POST on existing object but I keep getting 405. my ViewSet looks like this:
class Agents(viewsets.ModelViewSet):
serializer_class = serializer.AgentSerializer
model = serializer_class.Meta.model
....
and in the urls:
router = routers.SimpleRouter()
router.register(r'rest/agents', api_views.Agents, "Agent")
...
urlpatterns += router.urls
I call the post request from within APITestCase class (rest testing), my post request looks like this:
response = self.client.post(url, {'available': True, 'online':True}, format='json')
and printing url shows "/chat/rest/agents/1910_1567/", while 1910_1567 is the valid id of an existing agent (I create the agent during the setup and use its id).
I've seen other questions about rest post getting 405, but all the solutions there were url-related and in my case the url is correct. I even ran the setup outside the test and accessed the url via browser to get the object and the object indeed exist. but when I try to post to it - 405.
any ideas?
thanks!
Most probably your url is somehow matching with some different url's regex, and the request is being dispatched to some other view which disallows post request. Could you mention others urls in your urls.py? Infact you can verify this by adding pdb(debugger) in dispatch method of your view. If you made it to the pdb, then you can assume I was wrong.
If that is not the case, then you can evaluate the issue from dispatch method with debugger. Just in case you have any doubt about how to do that -
class Agents(viewsets.ModelViewSet):
serializer_class = serializer.AgentSerializer
model = serializer_class.Meta.model
def dispatch(self, *args, **kwargs):
import ipdb; ipdb.set_trace()
return super(Agents, self).dispatch(*args, **kwargs)
Solution Found:-
You are passing id in url, so this request would be routed to detail view and detail view only allow GET, PUT, DELETE operations on resource since resource already exists. So to create resource, don't provide the id. Otherwise use PUT request and provide support for creation in PUT.
POST are supposed to be for creation purpose. If you want to update with a ViewSet you'll need to PUT or PATCH (partial update) instead.
Edit:
For more about this, here's some explanation on the HTTP methods used for REST API:
http://restful-api-design.readthedocs.org/en/latest/methods.html
This is also described in the DRF documentation at:
http://www.django-rest-framework.org/api-guide/routers/#simplerouter