8
votes

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?

2

2 Answers

0
votes

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));
0
votes

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