Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django CSRF Token with Axios

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?

like image 384
RyanM Avatar asked Jul 19 '20 18:07

RyanM


1 Answers

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()
  },
}

like image 164
Валерий Аббакумов Avatar answered Nov 11 '22 19:11

Валерий Аббакумов