CSRF verification failed error with react, axios and DRF - django

I am trying to make a post request which looks like this
axios
.post(`http://127.0.0.1:8000/api/create/${this.props.id}`, {
headers: {
Authorization: `Token ${token}`
},
xsrfCookieName: "XSRF-TOKEN",
xsrfHeaderName: "X-CSRFToken"
})
.then();
I have added essential things in settings.py also, such as
CSRF_COOKIE_NAME = "XSRF-TOKEN"
I also have
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}

You may need to add ensure_csrf_cookie in your code.
A page makes a POST request via AJAX, and the page does not have an HTML form with a csrf_token that would cause the required CSRF cookie to be sent.
from django.views.decorators.csrf import ensure_csrf_cookie
#ensure_csrf_cookie
Read more about ensure_csrf_cookie. Let me know if that helps.

Related

Axios post request to route 'appName/v1/users/' (Djoser) throws 401 error but Postman doesn't

I'm new to Django and trying to build basic user authentication with REST API and a Vue.js frontend. To send the request, I am using axios, which is configured first in a seperate composable axios.js:
import axios from 'axios'
axios.defaults.withCredentials = true
axios.defaults.baseURL = 'http://localhost:8000'
and used inside a register.vue component:
const submitRegistration = () => {
axios.post('api/v1/users/', {username: 'userName', password: 'userPassword'})
.then(res => {
console.log(res)
})
}
To make it simple, I'm sending a data-object with predefined strings, as you can see above. The request gets sent to one of the djoser routes in projectName/urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('djoser.urls')),
path('api/v1/', include('djoser.urls.authtoken')),
]
This however, throws a 401 Unauthorized Error:
code: "ERR_BAD_REQUEST"
config: {transitional: {…}, adapter: Array(2), transformRequest: Array(1), transformResponse: Array(1), timeout: 0, …}
message: "Request failed with status code 401"
name: "AxiosError"
request: XMLHttpRequest {onreadystatechange: null, readyState: 4, timeout: 0, withCredentials: true, upload: XMLHttpRequestUpload, …}
response: {data: {…}, status: 401, statusText: 'Unauthorized', headers: AxiosHeaders, config: {…}, …}
stack: "AxiosError: Request failed with status code 401\n at settle (http://localhost:3000/node_modules/.vite/deps/axios.js?v=a45c5ec0:1120:12)\n at XMLHttpRequest.onloadend (http://localhost:3000/node_modules/.vite/deps/axios.js?v=a45c5ec0:1331:7)"
I've configured settings.py like so:
INSTALLED_APPS = [
...,
'corsheaders',
'rest_framework',
'rest_framework.authtoken',
'djoser'
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...,
]
CORS_ALLOWED_ORIGINS = ['http://localhost:3000']
CORS_ALLOW_CREDENTIALS = True
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
Edit:
The problem might be caused by authToken. In the root vue component app.vue, I am checking the store for an authentication token and setting an axios header accordingly:
onMounted(() => {
if (store.authToken) {
axios.defaults.headers.common['Authorization'] = `Token ${store.authToken}`
}
})
If I remove this code, the error turns into 400 BAD_REQUEST. Although I don't understand why one would need an authToken when registering a new user.
store.authToken is set during initialization:
import {reactive} from "vue";
export const store = reactive({
authToken: localStorage.authToken ?? null,
})
and shoud be null, when a new user is registered.
Edit2:
I followed some advice and used Postman to send the post request to http://localhost:8000/api/v1/users/ and it works. A new user is created.
My authentication check in the vue root component app.vue was flawed:
<script setup>
onMounted(() => {
if (store.authToken) {
axios.defaults.headers.common['Authorization'] = `Token ${store.authToken}`
}
})
</script>
store.authToken always returned true despite being null because it was fetched from localStorage being a string. JSON.parse() solved the problem.

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'

django-rest-framework login via Ajax returns HTML

I'm using the django-rest-framework and trying to login using Ajax, but the login API is returning HTML instead of JSON.
My configs:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
)
}
And the Ajax call:
$.ajax({
url: '/api-auth/login/',
method: 'POST',
data: "username=xxxxx&password=123",
headers: {
Accept: 'application/json'
},
success: function(resp) {
console.log(resp);
},
error: function(resp) {
console.error(resp);
}
});
Even if I'm specifying the Accept header, it always returns text/html.
Am I using the wrong endpoint?
I'm using JSONWebToken for external clients (ie, mobile) and the SessionAuthentication for same domain web requests. I expect the SessionAuthentication to set the cookie I can use to make further requests once logged in. If the login fails, I expect the API to return the error in JSON (ie, "Invalid username and password").
urls.py (important parts)
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns = [
url(r'^admin/', admin.site.urls),
...
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^api-token-auth/', obtain_jwt_token),
url(r'^api/', include(router.urls)),
]
Using:
Django==1.11
djangorestframework==3.7.7
djangorestframework-jwt==1.11.0
If you want to set a http only cookie you just have to add the parameter 'JWT_AUTH_COOKIE': 'you_cookie_name', inside the dictionary JWT_AUTH in settings.py also remember use the view that provide django_rest_framework_jwt located in from rest_framework_jwt.views import obtain_jwt_token in your case api-token-auth/'

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.