I'm using using django rest framework browsable api with ModelViewSet to do CRUD actions and want to use permissions.IsAuthenticatedOrReadOnly, but when I'm logged and try to DELETE or PUT I get
"detail": "CSRF Failed: CSRF token missing or incorrect."
My view looks like this
class objViewSet(viewsets.ModelViewSet):
queryset = obj.objects.all()
serializer_class = objSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
Serializer is just
class ObjSerializer(serializers.ModelSerializer):
class Meta:
model = Obj
Although when I delete permission_classes (so the default allowAny triggers) I can it works just fine.
What I want
To be able to PUT/DELETE only when I'm authenticated. I don't know how to send CSRF token, when all happens automatically (modalviewset does the whole work)
In your REST_FRAMEWORK settings you haven't mentioned the Authentication scheme so DRF uses the default authentication scheme which is SessionAuthentication. This scheme makes it mandatory for you to put csrf token with your requests. You can overcome this by:
To make this setting for the whole project in settings.py add
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
)}
To make this setting in specific view do the following in your view
class objViewSet(viewsets.ModelViewSet):
queryset = obj.objects.all()
serializer_class = objSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
authentication_classes = (BasicAuthentication,)
source: http://www.django-rest-framework.org/api-guide/authentication/#sessionauthentication
BTW, csrf token is saved as a cookie called 'csrftoken'. You can retrieve it from HTTP Response and attach it to your request header with the key 'X-CSRFToken'. You can see some details of this on: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
You might have used SessionAuthentication and session auth checks for the csrf_token always and you can avoid the checks by exempting but Lets not do that.
I think you can keep both Authentication classes. or just TokenAuthentication. ie,
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
# 'oauth2_provider.ext.rest_framework.OAuth2Authentication',
# 'rest_framework_social_oauth2.authentication.SocialAuthentication',
),
And If you are not going for TokenAuth and just the session Auth. you can always pass csrf_token via X-CSRFToken header. Or you just can go for csrf_except things. which will avoid the csrf missing issues.
This should work. Also refer the links below.
ref: https://stackoverflow.com/a/26639895/3520792
ref: https://stackoverflow.com/a/30875830/3520792
You shoud add CSRF token to your request. If you do it with JSON request you should add this in your JS code.
It adds CSRF token from user cookies.
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
You can remove CSRF check on individual urls. Try this on your urls.py:
url(r'^/my_url_to_view/', csrf_exempt(views.my_view_function), name='my_name'),
Related
I am building an ecom website and in order to implement an add_to_cart function I've done the following.
Clicking the add to cart button calls the javascript add_to_cart function that I wrote:
<button type="button" onclick = "add_to_cart({{ product.pk }})">Add to Cart</button>
This is the function:
function add_to_cart(product_pk) {
let url = '/add-to-cart/' + product_pk.toString()
$.ajax({
type: 'GET',
url: url,
processData: false,
contentType: false
})
}
the urls for this look like this:
path('add-to-cart/<str:product_pk>', views.add_to_cart, name='add_to_cart')
and finally my view looks like this:
def add_to_cart(request, product_pk):
cart = request.session['cart']
cart.append(product_pk)
request.session['cart'] = cart
context = {'length_of_cart': len(cart)}
return HttpResponse(content = dumps(context), content_type='application/json')
tldr: Click button, button calls js, js makes get request to url, url triggers view, logic in view adds product to cart.
I feel like this is pretty "hacky". Are there any security issues involved with what I've done here?
I feel like this is pretty "hacky". Are there any security issues involved with what I've done here?
A GET request is not supposed to have side-effects. Indeed, as the HTTP specifications say [w3.org]:
In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered “safe”.
You should make use of POST, PUT, PATCH or DELETE to make requests with side effects.
Django will automatically try to validate a CSRF token if you make a POST request. This to prevent a vulnerability that could result in a malicious JavaScript file that uses the credentials of the logged in user to make requests. Django (aims to) prevent this by using a CSRF token. You add such token to the POST request as is explained in the AJAX section of the documentation:
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
function add_to_cart(product_pk) {
let url = '/add-to-cart/' + product_pk.toString()
$.ajax({
type: 'POST',
url: url,
processData: false,
contentType: false,
headers: {'X-CSRFToken': csrftoken}
})
}
finally we should product the view to only accept POST requests with the #require_POST decorator [Django-doc]:
from django.views.decorators.http import require_POST
#require_POST
def add_to_cart(request, product_pk):
# …
I don't use a django form, we only process it with API and respond to the result.
I want to handle it without using #csrf_exempt.
When using a form, I know that you are using a tag, but in this case, it is difficult to write a tag. I can't get rid of csrf so I need help.
When receiving a request as a post, "CSRF token missing or incorrect." Appears. How can I solve this problem?
If this is a stateless API (i.e. you don't use cookies) you can safely disable CSRF as follows:
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def post(request):
return 'page'
If you need the csrf token check the csrf doc.
You can add the given code to a global js file and then reference it anywhere. I'm including the code here, but it is the same in the docs.
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
And then to get the csrf token:
const csrftoken = getCookie('csrftoken');
Here is the an example of how I use it in my fetch:
fetch('some_url', {
method: 'POST',
headers:{
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': csrftoken,
},
body: JSON.stringify({
some_key: some_var,
...
})
})
.then(response => {
jsonResponse = response.json();
status_code = response.status;
if(status_code != 200) {
alert('error');
} else {
alert('success');
}
})
.catch(error => {
console.log(error)
})
But make sure the csrf token is available in your template by including the csrf template tag {% csrf_token %}
Django 2.2, python 3.6
I'm using 2 authentication backends in my application :
GRAPHENE = {
'SCHEMA': 'my_main_app.all_schemas.schema',
'MIDDLEWARE': [
'graphql_jwt.middleware.JSONWebTokenMiddleware',
],
}
AUTHENTICATION_BACKENDS = [
'graphql_jwt.backends.JSONWebTokenBackend',
'django.contrib.auth.backends.ModelBackend',
]
I can get user info from graphql queries method like :
#login_required
def resolve_curr_user(self, info, parent_id):
user = info.context.user
But I cannot get user from a view :
def curr_user(request):
request.user # user is None
I'm passing the correct headers to the view request :
const headers = new Headers();
headers.append('Authorization', `JWT ${auth_token}`);
const init = {
method: 'GET',
headers: headers,
mode: 'cors',
cache: 'default'
};
const response = await fetch(`/curr_user_route`, init);
When authenticating for graphql query, django calls authenticate method from JSONWebTokenBackend class.
How do I tell django to call the same method for regular views ?
Hi i'm currently have my api that use this simple-JWT package for jwt token authentication, it worked great. But now when i try to call the api from the django website app using Ajax in which is from a page user already logged in but it still required me to use the jwt access_token.
My ajax call from the page user already logged in:
$.ajax({
type: "POST",
url: "/api/add_favorite/" + property_id + "/",
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer {{ refresh_token }}');
},
success: function (data) {
if (data.code == 200) {
alert('added to favorite');
replace_part_1 = '<a id="mylink2" href="#" value="' + property_id +'"><i class="fas fa-heart fa-lg" style="color: red" title="Remove from favorite"></i></a>'
$("a[value='" + property_id + "']").replaceWith(replace_part_1);
}
}
});
Now i don't want to set the header with authorization since in the page user already logged in so the session is already set.
So i tried to add Django Session authentication to the the api like so:
#api_view(['POST'])
#authentication_classes([SessionAuthentication, JWTAuthentication])
#permission_classes([IsAuthenticated])
def add_favorite(request, property_id):
if request.method == 'POST':
try:
favorite_property = Property.objects.get(pk=property_id)
if request.user.is_authenticated:
login_user = request.user
if not login_user.properties.filter(pk=property_id).exists():
login_user.properties.add(favorite_property)
return JsonResponse({'code':'200','data': favorite_property.id}, status=200)
else:
return JsonResponse({'code':'404','errors': "Property already exists in favorite"}, status=404)
except Property.DoesNotExist:
return JsonResponse({'code':'404','errors': "Property not found"}, status=404)
My Ajax after removed the header :
$.ajax({
type: "POST",
url: "/api/add_favorite/" + property_id + "/",
},
success: function (data) {
if (data.code == 200) {
alert('added to favorite');
replace_part_1 = '<a id="mylink2" href="#" value="' + property_id +'"><i class="fas fa-heart fa-lg" style="color: red" title="Remove from favorite"></i></a>'
$("a[value='" + property_id + "']").replaceWith(replace_part_1);
}
}
});
and i removed the set header from the Ajax call now i get 403 return code :
Failed to load resource: the server responded with a status of 403
(Forbidden)
My settings:
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
# 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
I don't know why the session authentication doesn't work since the Ajax call is from the page user already logged in.
Thank for reading!
Because you're adding Authentication header on your ajax requests, Django automatically use TokenAuthentication if Authentication exists on the request header. Remove it to use SessionAuthentication.
There might be a problem when you're switch to use SessionAuthentication is that Django will reject your unsafe requests if there is no CSRF token, more detail here
When using {% url 'query' %} inside an AJAX get call is returning a string but when I put a static url it works properly.
I'm using Django-Filters and Django-rest-framework in installed apps.
url.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^query/$', 'my_app.views.app_function', name='query')
]
app.js
$(document).ready(function(){
// LOAD COOKIE
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function callServer () {
$.ajax({
type: 'GET',
url: "{% url 'query' %}",
success: function (json) {
console.log(json)
},
error: function(x, t, m) {
if(t==="timeout") {
alert("got timeout");
} else {
alert(t);
}
},
headers: {
'X-CSRFToken': csrftoken
}
});
}
$("#query").click(function () {
$('#sub').submit(function (e) {
e.preventDefault();
});
return callServer();
});
});
views.py
class AppFilter(django_filters.FilterSet):
class Meta:
model = Post
fields = ['first', 'second']
#api_view(['GET'])
def app_function(request):
qs = Post.objects.all()
f = AppFilter(request.GET, queryset=qs)
serializer = PostSerializer(f, many=True)
return Response(serializer.data)
forms.py
class QueryForm(forms.Form):
first = forms.TypedChoiceField(
widget=forms.Select,
choices=choice_dict1
)
second = forms.TypedChoiceField(
widget=forms.Select,
choices=choice_dict2
)
Any help before I burn the place?
Django would not be able to resolve {% url 'query' %} in JS file, since that's client side stuff. reverse url is resolved at the time of html file rendering.
What you can do is, pass that url as init() function of that JS module from html file:
<!-- In your Template file -->
<script>
$(function(){
app.init("{% url 'query' %}");
});
</script>
And export the app module from js file. Set that url as a variable, and use it in ajax call.
The problem is that your javascript file (app.js) is probably not the template. You don't show it, but I assume you have an HTML file that is loading the app.js file. The HTML file is where the template variables and such will get expanded. The simplest way to resolve this is to embed the javascript code into your HTML file inside a <script> tag.
There is a library for Flask called Flask-JSGlue that solves this problem and lets you use template variables in your javascript files, but I cannot find a similar library for Django.