I am learning to work with Django Rest Framework and following the tutorial. I have create a simple index based on the tutorial, that works for GET, but not for POST:
#api_view(['GET','POST'])
def game_list(request):
if request.method == 'GET':
games = Game.objects.all()
serializer = GameSerializer(games, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = GameSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I set the default settings to AllowAny:
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny'
]
}
but I still get a HTTP 403 when I try to POST anything, using the Firefox RESTClient. I read that I have to add a X-CSRFToken header and cookie for this to work, but I do not have those.
From documentation:
By default, a ‘403 Forbidden’ response is sent to the user if an incoming request fails the checks performed by CsrfViewMiddleware. This should usually only be seen when there is a genuine Cross Site Request Forgery, or when, due to a programming error, the CSRF token has not been included with a POST form.
Also, as stated if the official documentation, CSRF is enabled by default, and you need to add a X-CSRFToken in your AJAX requests.
Here is the code from the documentation:
// using jQuery
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));
}
Take in mind that the documentation suggest to use ajaxSetup method from jquery, which is not a recommended way to do it because it can alter the way that others scripts uses the ajax function, so it's better to add the specific CSRF's code in your custom JS code like this:
$.ajax({
method: 'POST',
url: 'your.url.com/',
beforeSend: function(xhr, settings) {
if (!WU._csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
},
success: function(msg)
{}
});
Reference: https://docs.djangoproject.com/en/1.9/ref/csrf/#ajax
Related
I created a login API using Django rest framework and then used session auth.
When i sent request via Postman , i get csrftoken and sessionid cookies.
and i was able to access content on backend.
OK fine.
But when i built small login form html and called that API for logging in. It worked.
I see COOKIES IN RESPONSE BUT COOKIES ARE NOT SET IN CHROME BROWSER.
Under Storage section in dev tools cookies are empty.
when i tried to access content(other views/apis) , i was not able to..
I think its because of Cookies are not being stored in browser..
Been on this like 5 days. Please can Someone explain about cookies not being saved.?
View.py
class Login(APIView):
authentication_classes = [SessionAuthentication,]
def post(self, request, format=None):
username = request.POST.get("username", "")
print(request.session)
password = request.POST.get("password", "")
user = authenticate(request,username=username,password=password)
if user is not None:
login(request,user)
print(user)
return Response('Yes')
else :
return Response('No')
class List(APIView):
authentication_classes = [SessionAuthentication,]
permission_classes = [IsAuthenticated,]
def get(self, request, format=None):
return Response("Ark")
My Axios Request for login :
let s = this;
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
myHeaders.append("Authorization", "Basic cjox");
myHeaders.append("Access-Control-Allow-Credentials","*");
var urlencoded = new URLSearchParams();
var requestOptions = {
method: 'POST',
credentials: 'same-origin',
headers: myHeaders,
body: urlencoded,
redirect: 'follow'
};
axios.post("http://127.0.0.1:8000/api/login/",urlencoded,{headers:myHeaders},{withCredentials: true})
.then(res=>{
console.log(res.headers);
})
My other request :
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
myHeaders.append("Access-Control-Allow-Credentials","*");
var urlencoded = new URLSearchParams();
var requestOptions = {
method: 'GET',
credentials: 'same-origin',
headers: myHeaders,
redirect: 'follow'
};
axios.get("http://127.0.0.1:8000/api/d/",{headers:myHeaders},{withCredentials: true});
Yes, there is a similar question without resolution to the actual question.
My users may be trying out the app and then decide to register for an account to save. Once registered I need to log them in without redirecting or reloading the page. I've got the registration working and logging them in is no problem, but what do I return to the client in order for the logged in session to be active in the browser? I'm not looking to add any other frameworks or libraries to the stack, please.
Django View (some omitted)
class CheckAuthTokenAjaxView(FormView):
form_class = AuthTokenForm
def post(self, *args, **kwargs):
form = self.form_class(self.request.POST)
u = get_user_model().objects.filter(email=email).first()
u.email_confirmed = True
u.save()
login(self.request, u)
# What should I be sending back?
return JsonResponse({"success": True}, status=200)
JS
Doc.Register.checkAuthTokenForm = function(event) {
event.preventDefault();
var form = document.getElementById('authtoken-form');
var data = new FormData(form);
d3.json('/users/ajax/checkAuthToken/', {
method: 'post',
body: data,
headers: {
// "Content-type": "charset=UTF-8",
"X-CSRFToken": Doc.CSRF,
}
})
.then(result => {
if (result.hasOwnProperty('errors')) {
// ...
} else {
// WHAT DO I DO HERE?
}
});
}
This is an educational project, not for production. I wasn't intending to have user logins as part of this.
Can I make POST calls to Django with a CSRF token without having user logins? Can I do this without using jQuery? I'm out of my depth here, and surely conflating some concepts.
For the JavaScript side, I found this redux-csrf package. I'm not sure how to combine it with my POST action using Axios:
export const addJob = (title, hourly, tax) => {
console.log("Trying to addJob: ", title, hourly, tax)
return (dispatch) => {
dispatch(requestData("addJob"));
return axios({
method: 'post',
url: "/api/jobs",
data: {
"title": title,
"hourly_rate": hourly,
"tax_rate": tax
},
responseType: 'json'
})
.then((response) => {
dispatch(receiveData(response.data, "addJob"));
})
.catch((response) => {
dispatch(receiveError(response.data, "addJob"));
})
}
};
On the Django side, I've read this documentation on CSRF, and this on generally working with class based views.
Here is my view so far:
class JobsHandler(View):
def get(self, request):
with open('./data/jobs.json', 'r') as f:
jobs = json.loads(f.read())
return HttpResponse(json.dumps(jobs))
def post(self, request):
with open('./data/jobs.json', 'r') as f:
jobs = json.loads(f.read())
new_job = request.to_dict()
id = new_job['title']
jobs[id] = new_job
with open('./data/jobs.json', 'w') as f:
f.write(json.dumps(jobs, indent=4, separators=(',', ': ')))
return HttpResponse(json.dumps(jobs[id]))
I tried using the csrf_exempt decorator just to not have to worry about this for now, but that doesn't seem to be how that works.
I've added {% csrf_token %} to my template.
This is my getCookie method (stolen from Django docs):
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 = 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;
}
I've read that I need to change the Axios CSRF info:
var axios = require("axios");
var axiosDefaults = require("axios/lib/defaults");
axiosDefaults.xsrfCookieName = "csrftoken"
axiosDefaults.xsrfHeaderName = "X-CSRFToken"
Where do I stick the actual token, the value I get from calling getCookie('csrftoken')?
This Q&A is from 2016, and unsurprisingly I believe things have changed. The answer continues to receive upvotes, so I'm going to add in new information from other answers but leave the original answers as well.
Let me know in the comments which solution works for you.
Option 1. Set the default headers
In the file where you're importing Axios, set the default headers:
import axios from 'axios';
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "csrftoken";
Option 2. Add it manually to the Axios call
Let's say you've got the value of the token stored in a variable called csrfToken. Set the headers in your axios call:
// ...
method: 'post',
url: '/api/data',
data: {...},
headers: {"X-CSRFToken": csrfToken},
// ...
Option 3. Setting xsrfHeaderName in the call:
Add this:
// ...
method: 'post',
url: '/api/data',
data: {...},
xsrfHeaderName: "X-CSRFToken",
// ...
Then in your settings.py file, add this line:
CSRF_COOKIE_NAME = "XSRF-TOKEN"
Edit (June 10, 2017): User #yestema says that it works slightly different with Safari[2]
Edit (April 17, 2019): User #GregHolst says that the Safari solution above does not work for him. Instead, he used the above Solution #3 for Safari 12.1 on MacOS Mojave. (from comments)
Edit (February 17, 2019): You might also need to set[3]:
axios.defaults.withCredentials = true
Things I tried that didn't work: 1
I've found out, that axios.defaults.xsrfCookieName = "XCSRF-TOKEN";
and CSRF_COOKIE_NAME = "XCSRF-TOKEN"
DOESN'T WORK IN APPLE Safari on Mac OS
The solution for MAC Safari is easy, just change XCSRF-TOKEN to csrftoken
So, in js-code should be:
import axios from 'axios';
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "csrftoken";
In settings.py:
CSRF_COOKIE_NAME = "csrftoken"
This configuration works for me without problems Config axios CSRF django
import axios from 'axios'
/**
* Config global for axios/django
*/
axios.defaults.xsrfHeaderName = "X-CSRFToken"
axios.defaults.xsrfCookieName = 'csrftoken'
export default axios
After spending too many hours researching, and implementing the above answer, I found my error for this problem! I have added this answer to be supplemental of the accepted answer. I had set up everything as mentioned, but the gotcha for me was actually in the browser itself!
If testing locally, make sure you are accessing react through 127.0.0.1 instead of localhost! localhost handles request headers differently and doesn't show the CSRF tokens in the header response, where as 127.0.0.1 will! So instead of localhost:3000 try 127.0.0.1:3000!
Hope this helps.
The "easy way" almost worked for me. This seems to work:
import axios from 'axios';
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "XCSRF-TOKEN";
And in the settings.py file:
CSRF_COOKIE_NAME = "XCSRF-TOKEN"
You could add the Django-provided CSRF token manually into all of your post requests, but that's annoying.
From the Django docs:
While the above method (manually setting CSRF token) 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 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.
The docs have code you can use to pull the CSRF token from the CSRF token cookie and then add it to the header of your AJAX request.
There is actually a really easy way to do this.
Add axios.defaults.xsrfHeaderName = "X-CSRFToken"; to your app config and then set CSRF_COOKIE_NAME = "XSRF-TOKEN" in your settings.py file. Works like a charm.
For me, django wasn't listening to the headers that I was sending. I could curl into the api but couldn't access it with axios. Check out the cors-headers package... it might be your new best friend.
I fixed it by installing django-cors-headers
pip install django-cors-headers
And then adding
INSTALLED_APPS = (
...
'corsheaders',
...
)
and
MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
into my settings.py
I also had
ALLOWED_HOSTS = ['*']
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CORS_EXPOSE_HEADERS = (
'Access-Control-Allow-Origin: *',
)
in my settings.py although that is probably overkill
In addition to what yestema said (and echoed by krescruz, cran_man, Dave Merwin et. al), You also need:
axios.defaults.withCredentials = true
I am now making about user registration like this. I already allow any permission to make this request. But, when I post from postman, I got
"detail": "CSRF Failed: CSRF token missing or incorrect."
How shall I do?
class RegistrationView(APIView):
""" Allow registration of new users. """
permission_classes = (permissions.AllowAny,)
def post(self, request):
serializer = RegistrationSerializer(data=request.DATA)
# Check format and unique constraint
if not serializer.is_valid():
return Response(serializer.errors,\
status=status.HTTP_400_BAD_REQUEST)
data = serializer.data
# u = User.objects.create_user(username=data['username'],
# email=data['email'],
# password='password')
u = User.objects.create(username=data['username'])
u.set_password(data['password'])
u.save()
# Create OAuth2 client
name = u.username
client = Client(user=u, name=name, url='' + name,\
client_id=name, client_secret='', client_type=1)
client.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
CSRF have nothing to do with permission classes. APIView are by default csrf-exempt. A very good discussion on APIView and csrf here
Because APIViews can both be used from a browser (which needs CSRF protection) or a server (which does not need CSRF protection) by default the CSRF protection is turned off. However, also by default an APIView has an authentication class called SessionAuthentication. This class will dynamically re-apply django's CSRF middleware if the current request object contains an active user.
So most probably you are getting that error because of you already logged in.
You may find more details here:
https://docs.djangoproject.com/en/1.7/ref/contrib/csrf/
Form:
<form action="." method="post">{% csrf_token %}
AJAX:
Get CSRF from cookie:
// using jQuery
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');
JQuery AJAX:
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
function sameOrigin(url) {
// test that a given url is a same-origin URL
// url could be relative or scheme relative or absolute
var host = document.location.host; // host + port
var protocol = document.location.protocol;
var sr_origin = '//' + host;
var origin = protocol + sr_origin;
// Allow absolute or scheme relative URLs to same origin
return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
(url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
// or any other URL that isn't scheme relative or absolute i.e relative.
!(/^(\/\/|http:|https:).*/.test(url));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
// Send the token to same-origin, relative URLs only.
// Send the token only if the method warrants CSRF protection
// Using the CSRFToken value acquired earlier
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
As you are using Session Authentication, it usually requires CSRF to be checked. So you must pass the CSRF token in the X-CSRFToken header.
Since you are in fact have not logged in, you have to use csrf_exempt decorator.
from django.views.decorators.csrf import csrf_exempt
class RegistrationView(APIView):
permission_classes = (permissions.AllowAny,)
#csrf_exempt
def post(self, request):
# Do what needs to be done
pass
Consider the following survey question:
Do you like this question? (choose one)
Yes
No, because
I don't like its formatting
I don't like the wording
Other reason: ...................
Other response (please specify) ......................
I'm trying to represent a series of questions, some of which are like that. Some are more simple (just a list of choices). But I'm having some problems trying to square this off with Django and its own way of doing forms. We have the following problems:
We need server side validation. Only one choice can be specified. And in the case of the "other" choices above, those need a follow-up charfield.
We need to squeeze a charfield into the options! I'm fairly sure I can hack these in via the templates but keep that in mind.
Just to complicate things, the questions and their answers need to be editable. I've done this with YAML already and to the point of generating the form, that works fine.
So what's the best way to hack the Django form system to allow me to do this? Should I bother with django.forms at all or just write something that does everything in its own way? How would you do this?
If you want to avoid dealing with forms I suggest using jquery's $.ajax() method. Basically, you simply need to create a blank form model to catch the POST, then you can grab the data and do with it what you want. Here is an example:
#models.py
class BlankForm(forms.Form):
def __unicode__(self):
return "BlankForm"
#views.py
def my_view(request):
if request.method == 'POST':
if 'answer' in request.POST:
form = BlankForm(request.POST)
if form.is_valid():
foo = request.POST.__getitem__('add')
bar = request.POST.__getitem__('bar')
baz = request.POST.__getitem__('baz')
#Do stuff with your data:
return HttpResponse('ok')
Then in your webpage you could some something like this:
<script type="text/javascript">
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;
}
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
var csrftoken = getCookie('csrftoken');
$.ajaxSetup({
crossDomain: false, // obviates need for sameOrigin test
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
$.ajax({
type: 'POST',
data: {
'answer': true,
'foo': foo,
'bar': bar,
'baz': baz
},
dataType: 'application/json'
});
}
</script>
All the stuff about the cookies and CSRF token have to do with django's CSRF protection system. Basically all you would need to worry about would be editing the Data field in the $.ajax() method
The various options would be a ForeignKey to another table containing the responses. You'd have to have multiple fields in that FK to hold both the main answer and the subanswer, and you'd have to regroup (or reorder, if you need an explicit order) apropriately. You'd also need a CharField in order to hold the "other" response. It might be easiest to encapsulate both fields in a custom field, and use a custom widget to display the controls in the form.