Django CSRF Token with Axios - django
Situation:
I am attempting build a full SPA using Vue.js as my front end and Django as my back end. These systems are entirely separate (not a hybrid app with the index.html page served by the back end).
Approach
I created a services directory in my Vue-CLI generated project that provides the general accessibility for my REST API via the api.js file (contents below):
import axios from "axios";
import Cookies from "js-cookie";
axios.defaults.xsrfHeaderName = "X-CSRFToken";
axios.defaults.xsrfCookieName = "csrftoken";
const BackEnd = "http://127.0.0.1:8000/"; //local backend from manage.py runserver
export default axios.create({
baseURL: `${BackEnd}api/`,
timeout: 5000,
headers: {
"Content-Type": "application/json",
"X-CSRFToken": Cookies.get('csrftoken')
}
});
How do I know there is such a token to get? I wrote an API endpoint that provides the token in the Response headers (shown below):
Access-Control-Allow-Origin: *
Content-Length: 77
Content-Type: application/json
Date: Sun, 19 Jul 2020 18:04:06 GMT
Server: WSGIServer/0.2 CPython/3.7.6
Set-Cookie: csrftoken=HdM4y6PPOB44cQ7DKmla7lw5hYHKVzTNG5ZZJ2PqAUWE2C79VBCJbpnTyfEdX3ke; expires=Sun, 18 Jul 2021 18:04:06 GMT; Max-Age=31449600; Path=/; SameSite=Lax
Vary: Cookie, Origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Problem
While my Django REST Framework API is doing a create job serving up all the data for my GET requests, I cannot seem to assign the csrftoken properly to authenticate my POST requests. Even with the X-CSRFToken header appropriately set in my axios request, I still get the typical 403 (CSRF cookie not set) response from the server
Request Headers
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 247
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9vOu1sBaQrXtXseR
DNT: 1
Host: 127.0.0.1:8000
Origin: http://127.0.0.1:8080
Referer: http://127.0.0.1:8080/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
X-CSRFToken: T2Z7pzxKTAuCvBEIjkgRf8RGEEVLYfOyDYkYIcfkWCfSkPB76wCjMMizZvdTQPKg
UPDATE
Okay now this is just a pain! I've got a different token value in A) the Set-Cookie response header, B) the value for the csrftoken in my browser cookies, and C) in the axios POST request. Can anyone help me figure out what's going on here?
Django
you need youse djoser in django for authentication
wright
pip install djangorestframework-simplejwt
pip install djoser
settings.py changes
Add djoser in your INSTALLED_APPS
INSTALLED_APPS=[
...,
'djoser',
...
]
Add in your MIDDLEWERE
MIDDLEWERE=[
...,
'django.contrib.auth.middleware.AuthenticationMiddleware',
...
]
Add
# DRF settings
REST_FRAMEWORK = {
# Default permissions
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
],
# Token types
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework_simplejwt.authentication.JWTAuthentication",
"rest_framework.authentication.SessionAuthentication"
],
}
DJOSER = {
'PASSWORD_RESET_CONFIRM_URL':
'reset_password/{uid}/{token}',
'ACTIVATION_URL': 'activation/{uid}/{token}',
'SEND_ACTIVATION_EMAIL': True,
'SEND_CONFIRMATION_EMAIL': True,
'TOKEN_MODEL': None,
'HIDE_USERS': True,
'SERIALIZERS': {
},
'PERMISSIONS': {
'activation': ['rest_framework.permissions.AllowAny'],
'password_reset': ['rest_framework.permissions.AllowAny'],
'password_reset_confirm': ['rest_framework.permissions.AllowAny'],
'set_password': ['djoser.permissions.CurrentUserOrAdmin'],
'username_reset': ['rest_framework.permissions.AllowAny'],
'username_reset_confirm': ['rest_framework.permissions.AllowAny'],
'set_username': ['djoser.permissions.CurrentUserOrAdmin'],
'user_create': ['rest_framework.permissions.AllowAny'],
'user_delete': ['djoser.permissions.CurrentUserOrAdmin'],
'user': ['djoser.permissions.CurrentUserOrAdmin'],
'user_list': ['djoser.permissions.CurrentUserOrAdmin'],
'token_create': ['rest_framework.permissions.AllowAny'],
'token_destroy': ['rest_framework.permissions.IsAuthenticated'],
}
}
# JWT settings
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=2),
'REFRESH_TOKEN_LIFETIME': timedelta(days=5),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': True,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'AUTH_HEADER_TYPES': ('JWT',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(days=2),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=5),
}
In your app urls.py add djoser urls
urlpatterns = [
# DRF router
path('', include(router.urls)),
# djoser auth urls
url(r'^auth/', include('djoser.urls')),
# djoser auth jwt urls
url(r'^auth/', include('djoser.urls.jwt')),
# Login GUI DRF
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
Vue
Base API URL
project/src/api/common.js
import axios from 'axios'
export const HTTP = axios.create({
baseURL: 'http://api-url',
})
Base element project/src/api/element.js
import {HTTP} from './common'
function createHTTP(url) {
return {
async post(config) {
return HTTP.post(`${url}`, config).then(response => {
console.log(response)
return response.data
})
},
async get(element) {
return HTTP.get(`${url}${element.id}/`)
},
async patch(element) {
console.log(element)
return HTTP.patch(`${url}${element.id}/`, element).then(response => {
console.log(response)
return response.data
})
},
async delete(id) {
HTTP.delete(`${url}${id}/`)
return id
},
async list(queryParams = '') {
return HTTP.get(`${url}${queryParams}`).then(response => {
return response.data.results
})
}
}
}
export const Todos = createHTTP(`/todos/`)
Your store project/src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import todos from "#/store/modulse/todos";
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
todos,
}
})
Your mutations types project/src/store/mutation-types.js
export const SET_TODOS ='SET_TODOS'
export const PATCH_TODO ='PATCH_TODO'
export const DELETE_TODO ='DELETE_TODO'
export const CREATE_TODO ='CREATE_TODO'
Your module project/src/store/modulse/todos.js
import {
Todos,
} from '#/api/elements'
import {
SET_TODOS, PATCH_TODO, DELETE_TODO, CREATE_TODO
} from '../mutation-types'
// Getters
export default {
state: {
todos: []
},
getters: {
getTodos(state) {
return state.todos
},
},
// Mutations
mutations: {
[SET_TODOS](state, todos) {
state.todos = todos
},
[PATCH_TODO](state, todos) {
let id = todos.id
state.todos.filter(todos => {
return todos.id === id
})[0] = todos
},
[CREATE_TODO](state, todo) {
state.todos = [todo, ...state.todos]
},
[DELETE_TODO](state, {id}) {
state.todos = state.todos.filter(todo =>{
return todo.id !==id
})
},
},
// Actions
actions: {
async setTodos({commit}, queryParams) {
await Todos.list(queryParams)
.then(todos => {
commit(SET_TODOS, todos)
}).catch((error) => {
console.log(error)
})
},
async patchTodo({commit}, todoData) {
await Todos.patch(todoData)
.then(todo => {
commit(PATCH_TODO, todo)
}).catch((error) => {
console.log(error)
})
},
async deleteTodo({commit}, todo_id) {
await Todos.delete(todo_id)
.then(resp => {
commit(DELETE_TODO, todo_id)
}).catch((error) => {
console.log(error)
})
},
async createTodo({commit}, todoData) {
await Todos.create(todoData)
.then(todo => {
commit(CREATE_TODO, todo)
}).catch((error) => {
console.log(error)
})
},
}
In your project/src/main.js
import Vue from 'vue'
import store from './store'
import App from './App.vue'
import Axios from 'axios'
Vue.prototype.$http = Axios;
new Vue({
store,
render: h => h(App),
}).$mount('#app')
In your project/src/App.vue
import {mapActions, mapGetters} from "vuex";
export default {
name: 'App',
components: {},
data() {
return {}
},
methods: {
...mapActions(['setTodos','patchTodo','createTodo','deleteTodo']),
},
computed: {
...mapGetters(['getTodos']),
},
async mounted() {
await this.setTodos()
},
}
I simply used this in my vue app, and everything worked smoothly.
axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
axios({
method: 'post',
url: 'http://127.0.0.1:8000/api/orders-update',
xstfCookieName: 'csrftoken',
xsrfHeaderName: 'X-CSRFToken',
data: updateIDs,
headers: {
'X-CSRFToken': 'csrftoken',
}
}).then(response => console.log(response));
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.
How can I solve the problem as mentioned below?
I am new to Django and while I was doing some stuff with Django Framework, I came across some problems My Python Code [views.py] def create(request): if request.method == "POST": print(request.POST) and My JavaScript Code is const formdata = new FormData() formdata.append('itemOne', itemValue) formdata.append('itemTwo', itemValue) fetch("/create", { method: 'POST', credentials: 'same-origin', body: formdata, headers:{ // 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-CSRFToken': getCookie("csrftoken") }, }) .then(response => response.json()) .then(response => { console.log(response) }) .catch(err => { console.log(err) }) when I make a POST request as above from my JavaScript code, I get an empty dictionary. What might be the problem here? this is my urls.py files' code from django.urls import path, include from . import views urlpatterns=[ path('', views.index, name='home'), path('create', views.create, name='backend') ] How can I resolve the problem? I tried solutions from different websites and videos and none of them worked.
Graphene-Django not Sending HTTPOnly JWT Cookie to React-Apollo
Hi so I've been making an app using Django and Graphene for a GraphQL server that will be accessed by a React client using Apollo. I'm using graphql_jwt for authentication. I have set up my GraphQL server and the client code to read the JWT but I've learned that localStorage is not safe enough and neither is a cookie. I managed to find a way to set an HTTPOnly cookie when making the tokenAuth request and the cookie persists and is functional in GraphiQL (localhost:8000). I managed to do this with jwt_cookie decorator from graphql_jwt.decorators from django.contrib import admin from django.urls import path from graphene_django.views import GraphQLView from django.views.decorators.csrf import csrf_exempt from graphql_jwt.decorators import jwt_cookie urlpatterns = [ path('admin/', admin.site.urls), path('graphql/', jwt_cookie(csrf_exempt(GraphQLView.as_view(graphiql=True)))) ] Unfortunately, when I call the tokenAuth endpoint from Apollo (localhost:3000) it is neither setting the cookie nor is can I access it from my afterware (makes sense now that I think about it). Neither the same-origin or include credentials work in the server HttpLink or the ApolloClient itself. import { ApolloClient, gql, ApolloProvider, HttpLink, from, useQuery, ApolloLink } from '#apollo/client'; import { onError } from '#apollo/client/link/error'; import { setContext } from 'apollo-link-context'; const errorLink = onError(({ graphQLErrors, networkError }) => { if (graphQLErrors) { graphQLErrors.forEach(({ message }) => { console.log(message); }); } if (networkError) { console.log(networkError.message) } }); const afterwareLink = new ApolloLink((operation, forward) => { return forward(operation).map(response => { const context = operation.getContext(); const { response: { headers } } = context; console.log(headers); return response; }); }); const link = from([ errorLink, setContext((operation) => { console.log("HITTING SET CONTEXT"); // const token = localStorage.getItem('authToken'); const token = Cookies.get('authToken'); console.log(token); return { headers: { Authorization: token ? `JWT ${token}` : '' } } }), afterwareLink, new HttpLink({ uri: 'http://localhost:8000/graphql/' }) ]); const client = new ApolloClient({ link, cache, typeDefs, credentials: 'include' }); Please ignore the refreshToken and authToken values. I set them with the js-cookie package. But as you can see the Headers are empty and weren't set on login on the client but they were on the server. QUESTION: How do I pass the jwt response headers to the frontend?
SO I was able to resolve this. I posted my resolution on Github https://github.com/flavors/django-graphql-jwt/issues/191
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'
react-django csrf token error using react-cookie package
import cookie from 'react-cookies'; const csrfToken = cookie.load('csrftoken') onSubmitSignUp = () => { fetch('http://127.0.0.1:8000/register/', { method: 'post', headers:{ 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }, body: JSON.stringify({ username: this.state.username, email: this.state.email, password: this.state.password }) }) .then(response => response.json()) .then(res => { console.log(res) }) } Here i am sending some data to django.to send csrf token i am using 'react-cookie' as per the documentation. but in python side it is showing Forbidden (CSRF cookie not set.): /register/ [11/Nov/2018 11:56:33] "POST /register/ HTTP/1.1" 403 2868