Getting Django, VUE, CORS and CSRF working with a real world example

戏子无情 提交于 2019-12-01 18:53:09

First of all you want to use SessionAuthentication:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
    )
}

This will enforce CSRF, except for anonymous users (more on this in a bit). For browser frontends the easiest solution is to have both the (browser) frontend and backend under the same domain - this lets you avoid CORS - as suggested by comments above. If you have other clients then just go with tokens (DRF tokens or JWT) - but these are not safe for browser usage due to the danger of XSS attacks (storing tokens in localStorage is inherently insecure).

As you are using axios, CSRF setup is dead easy:

import axios from 'axios'

axios.defaults.xsrfHeaderName = 'X-CSRFToken'
axios.defaults.xsrfCookieName = 'csrftoken'

So you should have safe sessions with CSRF enforced. Almost. To quote the linked page above:

Warning: Always use Django's standard login view when creating login pages. This will ensure your login views are properly protected.

CSRF validation in REST framework works slightly differently to standard Django due to the need to support both session and non-session based authentication to the same views. This means that only authenticated requests require CSRF tokens, and anonymous requests may be sent without CSRF tokens. This behaviour is not suitable for login views, which should always have CSRF validation applied.

This is icky - you either have to just use Django server-side views which makes your SPA design somewhat more complicated or recreate login and other auth views in DRF, with the caveat of using the @csrf_protect method decorator to enforce CSRF on these "anonymous" views. Obviously such views will break for token-using clients so you probably want to use different endpoints for these (maybe re-using the same base classes). So your browser login uses /auth/browser/login/ and your mobile login /auth/mobile/login/, the former wrapped using @csrf_protect.

Recreating login and other auth views from scratch should be done carefully after studying the contrib auth source code; for vanilla requirements I would recommend pre-existing solutions like django-rest-auth and django-all-auth. The django-rest-auth package however is not well designed for browser frontends and forces the usage of token generation, plus you would need to wrap the views as described above. On the other hand, all-auth provides AJAX responses for JS clients and might be a better bet.

By far the easiest way to resolve this is to serve everything from the same domain. You can have your CDN or proxy direct /api calls to one server and the rest to the frontend server. This way there is no need to worry about CORS at all.

To get this working, I think you're just missing withCredentials = true in AXIOS configuration. Django requires the CSRF cookie to be sent and cookies are not sent over cross origin requests when withCredentials is not set.

axios.interceptors.request.use(function (config) {
  config.withCredentials = true
  return config
})

Another setting that might be missing is Djano's SESSION_COOKIE_DOMAIN. You should set it like this:

SESSION_COOKIE_DOMAIN=".mywebsite.com"

That first dot is important because it tells Django and then the web browser to use the cookie for *.mywebsite.com including api.mywebsite.com.

If it all still fails, I suggest setting a breakpoint on Django's CSRF middleware to see what's missing to make it work.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!