简体   繁体   中英

Django (DRF) & React - Forbidden (CSRF cookie not set)

There are tens of questions that are essentially identical to the one I'm asking. However, none of their answers seem to be working for me.

I have a React front-end where I am using axios to send requests to the back-end. Example
const request = await axios.post('${BASE_URL}/logout/')

Most of the Django Rest Framework endpoints are made with ViewSets. However, I have a few that are custom and mostly made for authentication.

path('createaccount/', views.create_account),
path('me/', views.current_user),
path('logout/', views.logout),
path('login/', views.login),
path('resetpassword', views.reset_password),

For the development of this project I've included @csrf_exempt above these views because I didn't want to deal with it at the time. Now I'm nearing deployment and it's time to figure it out.

Some answers say I need to get a CSRF Token from Django which is stored in cookies and I need to pass that in the header of each request. Some answers say all I need to do is configure axios like

axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "XCSRF-TOKEN";

And it will "just work". I've tried adjusting my CSRF_COOKIE_NAME to various values to get this to work too.

Some answers even say to keep @csrf_exempt but that sounds like a very, very bad idea.

Do I actually need to generate/get a CSRF cookie? Do I include it with every request? Or is it just a configuration of axios?

I've got this problem once, I was using token authentication. That's how I solved it. But not sure If it is the best idea. I only used csrf_exempt for this view and all others views are viewsets.

@csrf_exempt
def get_current_user(request, *args, **kwargs):
    if request.method == 'GET':
        user = request.user
        serializer = UserDataSerializer(user)
        return JsonResponse(serializer.data, safe=False)

My middleware in settings.py

MIDDLEWARE = [
   'django.middleware.security.SecurityMiddleware',
   'django.contrib.sessions.middleware.SessionMiddleware',
   'corsheaders.middleware.CorsMiddleware',
   # 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
   'django.middleware.common.CommonMiddleware',
   'django.middleware.csrf.CsrfViewMiddleware',
   'django.contrib.auth.middleware.AuthenticationMiddleware',
   'django.middleware.locale.LocaleMiddleware',
   'oauth2_provider.middleware.OAuth2TokenMiddleware',
   'django.contrib.messages.middleware.MessageMiddleware',
   'django.middleware.clickjacking.XFrameOptionsMiddleware',
   'auditlog.middleware.AuditlogMiddleware',
]

To make CSRF protection work you will need CSRF cookie sent from Django to React as a response to some request (like login or sth else). It will set cookie using Set-Cookie on frontend side. So make sure that you have a view that does that on Django side. If not, create a view that as response generates that token.

How Django (4.04) CSRF validation work (simplified, based on middleware/csrf.py):

  1. gets CSRF token from cookie (so frontend needs to resend it back on another request) - it might also get it from session but in case of React I would not use it

    def _get_token(self, request): .... try: cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME] except KeyError: return None
  2. Compares that cookie CSRF token with non-cookie token from request:

     def _check_token(self, request): # Access csrf_token via self._get_token() as rotate_token() may have # been called by an authentication middleware during the # process_request() phase. try: csrf_token = self._get_token(request) except InvalidTokenFormat as exc: raise RejectRequest(f"CSRF cookie {exc.reason}.") if csrf_token is None: # No CSRF cookie. For POST requests, we insist on a CSRF cookie, # and in this way we can avoid all CSRF attacks, including login # CSRF. raise RejectRequest(REASON_NO_CSRF_COOKIE) # Check non-cookie token for match. request_csrf_token = "" if request.method == "POST": try: request_csrf_token = request.POST.get("csrfmiddlewaretoken", "") except UnreadablePostError: # Handle a broken connection before we've completed reading the # POST data. process_view shouldn't raise any exceptions, so # we'll ignore and serve the user a 403 (assuming they're still # listening, which they probably aren't because of the error). pass if request_csrf_token == "": # Fall back to X-CSRFToken, to make things easier for AJAX, and # possible for PUT/DELETE. try: request_csrf_token = request.META[settings.CSRF_HEADER_NAME] except KeyError: raise RejectRequest(REASON_CSRF_TOKEN_MISSING) token_source = settings.CSRF_HEADER_NAME else: token_source = "POST" try: request_csrf_token = _sanitize_token(request_csrf_token) except InvalidTokenFormat as exc: reason = self._bad_token_message(exc.reason, token_source) raise RejectRequest(reason) if not _does_token_match(request_csrf_token, csrf_token): reason = self._bad_token_message("incorrect", token_source) raise RejectRequest(reason)

As you can see you either need to include csrfmiddlewaretoken in POST request or include it in header with key: settings.CSRF_HEADER_NAME and value read from cookies on front-end side.

So for example you set withCredentials: true (to include initial cookie), read that initial CSRF cookie in React and add to header in axios request at specific key.

When in question, I would just debug request setting up breakpoints in this code of Django in middleware/csrf.py and you can trace what is missing and why CSRF validation fails.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM