0

I'm using Django 3.1.1 with Django's auth contrib module for managing users. I'm not using Django templates, but rather creating a Django API application to respond to requests from my React frontend. I have this in my settings.py file

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'directory.middleware.extend_token_response.ExtendTokenResponse'
]

#CORS_ORIGIN_ALLOW_ALL = True

ALLOWED_HOSTS = ['127.0.0.1', 'localhost', 'dev.mywebsite.com', 'prod.mywebsite.com', 'map.mywebsite.com']

CORS_ORIGIN_WHITELIST = [
    'http://localhost:3000', 'http://localhost:3001', 'http://127.0.0.1:3000', 'http://127.0.0.1:3001'
]

CORS_EXPOSE_HEADERS = [
    'Refresh-Token', 'Content-Type', 'Authorization', 'X-CSRFToken'
]
CORS_ALLOW_CREDENTIALS = True
CSRF_TRUSTED_ORIGINS = ['localhost', '127.0.0.1']

I have this set up in my views.py file

class ResetPasswordView(SuccessMessageMixin, PasswordResetView):
    email_template_name = 'users/password_reset_email.html'
    subject_template_name = 'users/password_reset_subject'
    success_message = "We've emailed you instructions for setting your password, " \
                      "if an account exists with the email you entered. You should receive them shortly." \
                      " If you don't receive an email, " \
                      "please make sure you've entered the address you registered with, and check your spam folder."
    success_url = reverse_lazy('users-home')

    def post(self, request, *args, **kwargs):
        print("request data %s" % request.data)
        email = request.data.get('email')
        try:
            if User.objects.get(email=email).active:
                print("email: %s " % email)
                return super(ResetPasswordView, self).post(request, *args, **kwargs)
        except:
            # this for if the email is not in the db of the system
            return super(ResetPasswordView, self).post(request, *args, **kwargs)

In my frontend, I submit password reset requests to my backend using this fetch code

  const handleFormSubmit = (e) => {
    e.preventDefault()
    const csrftoken = getCsrfToken();
    fetch(REACT_APP_PROXY + '/reset_password', {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'X-CSRFToken': csrftoken
      },
      body: JSON.stringify({username})
    }).then(resp => resp.json())
    .then(jsob => {
      if(!jsob.ok){
        console.log(jsob)
        setErrors(jsob)
      }
      if(jsob.token){
        sessionStorage.setItem('token', jsob.token)
        setRedirect(true)
      }
    })
  }

The problem is, I don't have a CSRF token the first time I submit the form because Django is not serving my React application (it is served by Apache). How do I get a CSRF token from Django so that I can submit it back during the submission of my password reset form?

Dave
  • 15,639
  • 133
  • 442
  • 830
  • Does this answer your question? [How to use csrf\_token in Django RESTful API and React?](https://stackoverflow.com/questions/50732815/how-to-use-csrf-token-in-django-restful-api-and-react) – aaron Jan 01 '23 at 07:11
  • Thanks but not really. That post discusses how to read a cookie once it is in local storage, but it doesn't discuss how to first get that cookie in local storage. – Dave Jan 01 '23 at 16:54
  • The question is why is the page served by apache and not from django? Cant you proxy the request to django and have it served from django directly? – Beikeni Jan 06 '23 at 13:27

2 Answers2

1

Disclaimer

This answer doesn't have an implementation in JS/ReactJS, but it has general instructions on how to have them.


Django will return the CSRF token with the cookies if you access valid views rendered with TemplateResponse. To get the CSRF token in your case, you need to send the HTTP GET request to the same URL (response may look like this). After completing the request, you will have the CSRF token in your cookies' section.

Here, I demonstrate a simple example with Python's requests library.

In [23]: import requests

In [24]: url = "http://127.0.0.1:1234/pwd-reset/"

In [25]: response = requests.get(url)

In [26]: response.status_code
Out[26]: 200

In [27]: response.cookies.get_dict()
Out[27]: {'csrftoken': 'PABeYmF2k7Pvzhzu2UgqB0P63Ann5WeKqsODLmpx8VUtQhdn1DbhFT1Gs52pOEO5'}
JPG
  • 82,442
  • 19
  • 127
  • 206
  • So the gist is I must first make a request for an HTML page, store the cookie, and then execute my API-only call? This seems like a bit of a hack. Is CSRF-usage not typical in an API-only application? All the examples online deal with standard stateless HTML pages, but modern responsive apps using single page design, at least as far as I can tell. – Dave Jan 07 '23 at 18:00
  • Your question is how to get the CSRF token, but, maybe, you should ask something like *"how to request password reset without CSRF Token (or via token auth or via DRF)"*. What do you think? – JPG Jan 08 '23 at 03:57
  • Happy to submit a form without a CSRF token, but isn't that sweeping the problem under the rug? If the point of CSRF is to help cut down on spam/brute force requests, what's the equivalent when dealing with an API-only application? – Dave Jan 11 '23 at 17:03
0

You can get csrf token value by targeting csrf hidden input like this...

views.py

def CreditSaveView(request):
    if request.method == 'POST':
        creditform = CreditForm(request.POST)
        if creditform.is_valid():
            amt = request.POST['amt']
            tag = request.POST['tag']
            crs = request.POST['csrfmiddlewaretoken']
            CreditModel(amt=amt,tag=tag).save()
            return JsonResponse({'status':'done'})

In tag

<script>
    document.getElementById("creditbtn").addEventListener("click", function () {
        let id_amt = document.getElementById('id_amt').value;
        let id_tag = document.getElementById('id_tag').value;
        let crs = document.getElementsByName('csrfmiddlewaretoken')[0].value
        my_data = { csrfmiddlewaretoken: crs, amt: id_amt, tag: id_tag };

        console.log(my_data)

        $.ajax({
            url: "/creditsave/",
            method: "POST",
            data: my_data,
            // dataType: "json",
            success: function (data) {
                if (data.status == 'done') {
                    // document.getElementById('creditform').reset()

                    document.getElementsByTagName('form')[1].reset()
                }
            }
        });
    });
</script>

  • Thanks but question is how do I get the CSRF token before I submit my POST request? I have a React app communicatinng with a Django API. When I submit my Reset password form to Django, I need to submit a CSRF token but I'm not clear how I get that initially. – Dave Jan 11 '23 at 17:01