Axios put request not working properly with Django Rest Framework - django

From axios I have to make a put request to the DRF. In DRF I'm using APIView class-based view. Before I was getting 403 forbidden error when I was making put request. Because it was not sending csrftoken in the request. I googled it and went through the CSRF with Django, React+Redux using Axios stack overflow question and somehow I was able to fix 403 forbidden issue.
Going through the above StackOverflow question I changed a few lines of codes and they are.
In my axios file
axios.defaults.xsrfHeaderName = "X-CSRFToken";
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.withCredentials = true;
In my project's settings. py file
ALLOWED_HOSTS = ['*']
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
But the wiered part is here when I pass any data to thorough put request from axios, it actually passing the flow to the APIView class (It's my assumption) from there I get a wired response. I could see that it sends the react's index.html as the data in the response.
My views.py code
class TestView(APIView):
def put(self, request):
print('inside put method')
print(request.data)
return Response('Test data passing in return')
My Axios code
import axios from "axios";
axios.defaults.xsrfHeaderName = "X-CSRFToken";
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.withCredentials = true;
return axios({
method: "put",
url: API_URL,
data: alterRecord
})
.then(res => {
console.log("inside then");
console.log(res);
})
.catch(e => {
console.log("Inside catch");
console.log(e.message);
});
In my APIView class for testing purpose, I've added print statements and it doesn't print those lines in the command line. When I saw the requests in the command line, I saw that OPTIONS method also being called I'm not sure why.
[09/Oct/2019 18:02:09] "OPTIONS /api/url/ HTTP/1.1" 200 0
[09/Oct/2019 18:02:09] "PUT /api/url/ HTTP/1.1" 200 2102
In the axios response data i can see that it's passing react's index.html data like this
data: "<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>NoA</title><link href="/static/css/2.39680177.chunk.css" rel="stylesheet"><link href="/static/css/main.87078788.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(l){function e(e){for(var r,t,n=e[0],o=e[1],u=e[2],f=0,i=[];f<n.length;f++)t=n[f],p[t]&&i.push(p[t][0]),p[t]=0;for(r in o)Object.prototype.hasOwnProperty.call(o,r)&&(l[r]=o[r]);for(s&&s(e);i.length;)i.shift()();return c.push.apply(c,u||[]),a()}function a(){for(var e,r=0;r<c.length;r++){for(var t=c[r],n=!0,o=1;o<t.length;o++){var u=t[o];0!==p[u]&&(n=!1)}n&&(c.splice(r--,1),e=f(f.s=t[0]))}return e}var t={},p={1:0},c=[];function f(e){if(t[e])return t[e].exports;var r=t[e]={i:e,l:!1,exports:{}};return l[e].call(r.exports,r,r.exports,f),r.l=!0,r.exports}f.m=l,f.c=t,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(r,e){if(1&e&&(r=f(r)),8&e)return r;if(4&e&&"object"==typeof r&&r&&r.__esModule)return r;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:r}),2&e&&"string"!=typeof r)for(var n in r)f.d(t,n,function(e){return r[e]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var r=window.webpackJsonp=window.webpackJsonp||[],n=r.push.bind(r);r.push=e,r=r.slice();for(var o=0;o<r.length;o++)e(r[o]);var s=n;a()}([])</script><script src="/static/js/2.46fe2ccd.chunk.js"></script><script src="/static/js/main.c9f4f998.chunk.js"></script></body></html>"
Here I'm not sure why print statements are not getting printed, why OPTION HTTP method is getting called here and also why am I getting react's index.html as a response instead of Response('Test data passing in return').
If I do the same request from postman it actually sends back the response correctly. But with axios it does not. I'm sure I've to do something with axios. Please, can anyone assist me with this?

Related

Django fetching data with HttpOnly cookie says 401 (Unauthorized)

I have been trying to use HttpOnly cookie with Django for two days but couldn't solve it yet. I tried adding all of these to my settings.py file
SESSION_COOKIE_HTTPONLY=True
SESSION_COOKIE_PATH = '/;HttpOnly'
LANGUAGE_COOKIE_HTTPONLY=True
CSRF_COOKIE_HTTPONLY=True
My home.html file
fetch('http://127.0.0.1:8000/api/books/', {
method: 'POST',
mode: 'same-origin',
credentials: 'include'
}).then(function(response) {response.json()}).then(
event=>console.log(event)
)
My api is working and i am signed in but it still gives me "401 (Unauthorized)" response. I searched in google for days and still couldn't solve it. I think i am missing something but don't know what
You have to explicitly set the csrf header.
As mentioned in Django docs
You should include csrf in your html template when using CSRF_USE_SESSIONS or CSRF_COOKIE_HTTPONLY is True
So somewhere in your template file add
<html>
<body>
{% csrf_token %}
...
</body>
</html>
make sure in your settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
]
}
SessionAuthentication is first
Then in your template file you need to retrieve the csrf from hidden element and add it to the fetch request headers
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
fetch('http://localhost:8000/api/books/', {
method: 'POST',
mode: 'same-origin',
credentials: 'include',
headers: {
'X-CSRFToken': csrftoken
}
}).then(function(response) {
response.json()}).then(
event=>console.log(event)
)
After this if you're logged in you'll be able to access the desired route.
(Make sure you are logged in into the admin, otherwise csrf will be passed in the request but you'll still get 403)

Django CSRF and axios post 403 Forbidden

I use Django with graphene for back-end and Nuxt for front-end. The problem appears when I try post requests from nuxt to django. In postman everything works great, in nuxt I receive a 403 error.
Django
# url.py
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', GraphQLView.as_view(graphiql=settings.DEBUG,
schema=schema)),
]
# settings.py
CORS_ORIGIN_WHITELIST = 'http://localhost:3000'
CORS_ALLOW_CREDENTIALS = True
CSRF_USE_SESIONS = False
CSRF_COOKIE_HTTPONLY = False
CSRF_COOKIE_SAMESITE = None
NuxtJs
# nuxt.config.js
axios: {
baseURL: 'http://127.0.0.1:8000/',
debug: false,
progress: true,
credentials: true
},
# plugins/axios.js
await $axios.onRequest((config) => {
config.headers.common['Content-Type'] = 'application/json'
config.xsrfCookieName = 'csrftoken'
config.xsrfHeaderName = 'X-CSRFToken'
const csrfCookie = app.$cookies.get('csrftoken')
config.headers.common['X-CSRFToken'] = csrfCookie
console.log(config)
# store/contact.js
import { AddMessage } from '../queries/contact.js'
export const actions = {
async send() {
const message = await this.$axios({
url: 'api/',
method: 'POST',
data: AddMessage
})
}
}
# queries/contact.js
export const AddMessage = {
query: `
mutation AddContact($input: AddMessageInput!){
addMessage(input: $input){
message{
name
email
body
terms
}
}
}
`,
variables: `
{
"input":{
"name": "test",
"email": "test#test.com",
"body": "test",
"terms": true,
}
}
`,
operationName: 'AddMessage'
}
Somethig that
Here are request headers from axios post. Something strange for me is the cookie with a wrong value. The good value of token is present in X-CSRFToken header.
Here is the log from axios post request. Another strange thing for me is the undefined headers: Content-Type and X-CSRFToken
Thank you!
I resolved this problem and I want to share the solution here.
The problem with wrong cookie value was generated by the front end app that managed (I don't remember how) to get csrf cookie from the back end app. In X-CSRFToken header was token received from response's Set-cookie header and in Cookie header was the cookie from back end app.
After I changed localhost with 127.0.0.1 and added
config.xsrfCookieName = 'csrftoken' in axios plugin
I was able to separate the apps, save and use cookies independent.
The second problem, with undefined headers was generated by axios. These 2 line of code resolved the problem. These were added also in axios onRequest method.
config.xsrfHeaderName = 'X-CSRFToken'
config.headers['Content-Type'] = 'application/json'

Can not send POST request to Flask app using axios

I'm simply trying to send a json post request using axios to Flask. But I get 'OPTIONS' in the server console which I understood is the preflight request. And I found if I use x-www-form-urlencoded instead of application/json in the headers of axios, the browser doesn't do a preflight request, so I was getting a POST request finally. But the block of POST request (as you can see in the code below) still doesn't get hit. I keep getting a CORS issue even though I've set the Access control allow origins in the server. What could be the problem here?
//FLASK SERVER
#bp.route("/", methods=["GET", "POST"])
def recipes():
if request.method == "GET":
# show all the recipes
recipes = [
{'name': 'BURGER', 'ingredients': ['this', 'that', 'blah']},
{'name': 'CHICKEN'}
]
return jsonify(recipes)
elif request.method == "POST":
# save a recipe
print('SEE HEREEE'+ str(request.data))
print(request.is_json)
content = request.get_json()
print(content)
return jsonify(content), 201, {'Access-Control-Allow-Origin': '*', 'Access-Control-Request-Method': "*", 'Access-Control-Allow-Headers': "*"}
//FRONTEND
try{
let response = await axios({
method: "POST",
url: "http://localhost:5000/recipes/",
headers: {
"Content-Type": "*"
},
data: {"hello": "HI"}
});
console.log("RESPONSE HERE", response)
}catch(err){
throw new Error("ERROR", err)
}
//Browser Console
If there is any error in Python code it shows similar error in front end side. From your screenshot, I see that there is an error in LoginForm. I think that is why the front end is not working as expected.
To handle CORS error, I use flask_cors Flask extension. Details of the package can be found in this Pypi package repository.
I have simplified the code to test if the CORS error occurs when there is no error in back end. I can add a new recipe using POST request from Axios.
Backend:
from flask import Flask, render_template, jsonify, request
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
#app.route("/recipes", methods = ['GET', 'POST'])
def recipes():
# recipes
recipes = [
{'name': 'BURGER', 'ingredients': ['this', 'that', 'blah']},
{'name': 'HOTDOG', 'ingredients': ['Chicken', 'Bread']}
]
if request.method == "GET":
return jsonify(recipes)
elif request.method == "POST":
content = request.get_json()
recipes.append(content)
return jsonify(recipes)
Frontend:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Frontend Application</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="result">
</div>
<script type="text/javascript">
axios.post('http://127.0.0.1:5000/recipes', {
name: 'Sandwich',
ingredients: ['Vegetables', 'Sliced cheese', 'Meat']
})
.then(function (response) {
var result = document.getElementById("result");
const data = response.data;
for(var i=0; i<data.length; i++){
const item = data[i];
result.innerHTML+=item.name+": "+item.ingredients+"<br>";
}
})
.catch(function (error) {
console.log(error);
});
</script>
</body>
</html>
Output:

Cannot return 404 error as json instead of html from a Flask-Restful app

I am working on a simple Flask REST API test and when I call the {{url}}/items for example I get the items list. However if a call is passed to an endpoint that does not exist for example {{url}}/itemsss then I get the error 404 in html.
I would like to make the error handling more friendly and return json instead of html for certain errors such as 400, 404,405...
For the 404 for example i tried this:
#app.errorhandler(404)
def not_found(e):
response = jsonify({'status': 404,'error': 'not found',
'message': 'invalid resource URI'})
response.status_code = 404
return response
However it does not work.
My issue is similar to this one: Python Flask - Both json and html 404 error
I wanted to know, if using the blueprints the only way to accomplish this?
If there a simpler way to output the 404 error as json?
For example instead of this:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
Something like this:
{
error: true,
status: 404,
code: "error.notFound",
message: "API endpoint not found",
data: { }
}
I appreciate your help with this.
Usually when I need to return a custom error message with Flask-RESTful I would do something like:
from flask import make_response, jsonify
def custom_error(message, status_code):
return make_response(jsonify(message), status_code)
I think I found the solution in the official documentation:
from flask import json
from werkzeug.exceptions import HTTPException
#app.errorhandler(HTTPException)
def handle_exception(e):
"""Return JSON instead of HTML for HTTP errors."""
# start with the correct headers and status code from the error
response = e.get_response()
# replace the body with JSON
response.data = json.dumps({
"code": e.code,
"name": e.name,
"description": e.description,
})
response.content_type = "application/json"
return response
Code and description are there in HTTP anyways, need only the message. And using jsonify from flask.
from flask import jsonify
from werkzeug.exceptions import HTTPException
#app.errorhandler(HTTPException)
def handle_exception(e):
return jsonify({"message": e.description}), e.code

Django React Axios

I am trying to make a post request to a Django server using React with Axios. However, I am getting a redirect 302 on the server side.
Just followed all suggestions in this post here CSRF with Django, React+Redux using Axios
unsuccessfully :(
However, what I have done so far is the following:
Sat the default axios CookieName and HeaderName (on the javascript side):
axios.defaults.xsrfHeaderName = "X-CSRFToken";
axios.defaults.xsrfCookieName = "XCSRF-Token";
Got this in settings.py as well:
CSRF_COOKIE_NAME = "XCSRF-Token"
And here is how the post request looks like:
axios(
{
method: 'post',
url: `/api/${selectedEntryType}_entry`,
data: {
"test": "test"
},
headers: {
'X-CSRFToken': document.cookie.split('=')[1],
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json',
}
}
)
Another thing that I have tried is to make the post request from the Django rest api UI:
and it does work successfully.
The only differences in the Request Headers when I make the request from the UI and from JS are:
Accept, Content-Length, and Referer, which I don't see how could they be problematic.
Please help.
Managed to fix it by changing the url (url:'/en/api/endpoint/') I was posting to, because apparently for a POST request:
You called this URL via POST, but the URL doesn't end in a slash and you have APPEND_SLASH set. Django can't redirect to the slash URL while maintaining POST data. Change your form to point to 127.0.0.1:8000/en/api/endpoint/ (note the trailing slash), or set APPEND_SLASH=False in your Django settings
After that I started getting Forbidden 403, but by adding:
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator
#method_decorator(csrf_protect)
def post(self, request):
return Response()
and also changed the defaults in JS to:
axios.defaults.xsrfHeaderName = "X-CSRFToken";
axios.defaults.xsrfCookieName = "csrftoken";
and removed CSRF_COOKIE_NAME = "XCSRF-Token" from settings.py.
It worked.
Hope this helps somebody in the future.