Consume REST Services with AJAX and CSRF protection in Django

What I have now?

  1. API endpoints with Schema provided
  2. They require Token Authentication

What I intend to do?

Create a form to interact with these endpoints and perform validation and preprocessing before calling REST service.

Motivation:

  1. Provide intuitive interaction UI to REST services.
  2. Reuse code, reuse existing REST service when creating extra presentation layer.
  3. Process form asynchronously (AJAX) without sacrificing powerful built-ins of Django Form

AJAX with CRSF protection

Create a simple form

We will start with creating a Form:

# forms.py
from django import forms

class MyForm(forms.Form):
    student_id = forms.CharField(max_length=10, required=True)
    student_name = forms.CharField(max_length=255, required=True)

    def clean_student_id(self):
        # Pre-process student_id
        return re.sub('\D', '', self.cleaned_date.get('student_id'))

Next, we create a simple template and view to render this form.

# in templates/my_form.html
# Add these into your body
<form action="">
    {% crsf_token %}
    {{ my_form.as_p }}
    <input type="button" id="btn-submit_my_form">
</form>

In your views.py:

# I'm using class-based view here

# Skip imports
class MyFormView(TemplateView):
    template_name = 'my_form.html'

    def get_context_data(self, **kwargs):
      context = super(MyFormView, self).get_context_data(**kwargs)
      context['my_form'] = MyForm()
      return context

After mapping your url to MyFormView, you should be able to view your form.

Add AJAX call

We want to submit our form asynchronously, AJAX is the only solution. Let's add some script in your my_form.html. I will be using jQuery syntax, make you have the jQuery library imported.

<script>
    $('#request-verification').on('click', function(event){
        event.preventDefault();
        $.ajax({
            url : "{% url 'ajax:my_form' %}", // the endpoint
            type : "POST", // http method
            data : { student_id : $('#id_student_id').val(),
                     student_name : $('#id_student_name').val()}

            success : function(json, textMsg, xhr) {
                alert('AJAX called successfully')
            },

            error : function(xhr,errmsg,err) {
                alert('Opps! Something's wrong'!)
            }
        });
    });
</script>

The callback above first prevent the default event being triggered by defining event.preventDefault(). In data attribute, we collect a dictionary of form inputs. Replace the URL to your respective URL name.

If the AJAX call successfully, we prompt an alert says that 'AJAX called successfully', whereas alert user with another message if error occurs.

Deal with CSRF

We do not want to sacrifice CSRF protection in Django, django recognize your incoming request with it's CSRF protection token in your request header. If you run your code above, you will get a CSRF validation error.

To deal with it, add this snippet of code into your template script tags (I prefer creating a separate .js file and import into my template):

# This snippet is provided in Django official documentation
    function getCookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie !== '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
    var csrftoken = getCookie('csrftoken');

    function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }
    $.ajaxSetup({
        beforeSend: function(xhr, settings) {
            if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", csrftoken);
            }
        }
    });

The code above will pre-process your AJAX request by adding a X-CSRFToken header before sending your request. Till now, your front-end is good to go. What about backend? Let us dive into our AJAX endpoint.

# Your ajax endpoint view, I am using function-based view.

@require_http_methods(['POST'])
def my_form_ajax_endpoint(request):
    # Process your data here.

Of course, at this point, you should be able to create an async call using AJAX. However, what I want is not simply this, I want to process my POST data with Django Form built-ins. Let our MyForm form class do the dirty work for us. We will only process it if the data are valid.

# Your ajax endpoint view, I am using function-based view.

@require_http_methods(['POST'])
def my_form_ajax_endpoint(request):
    # Process your data here.
    form = MyForm(request.POST)
    if form.is_valid():
        # We process further here..
        # I will talk about calling another REST service from here in Part 2
        return JsonResponse({"message":"form is submitted"}, status=201)

    return JsonResponse({"error":"invalid input"}, status=400)

As of this point, async call using AJAX with CSRF preserved can be done in a breeze.

First published on 2017-11-11

Republished on Hackernoon

No Comments Yet