paint-brush
Consume REST Services with AJAX and CSRF protection in Djangoby@melvinkcx2
9,775 reads
9,775 reads

Consume REST Services with AJAX and CSRF protection in Django

by Melvin KohMarch 29th, 2018
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

<em>Originally published at </em><a href="http://www.melvinkoh.me/#/post/b6f545a3-5ad5-4c5e-96aa-751cff328559" target="_blank"><em>melvinkoh.me</em></a><em>.</em>

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Consume REST Services with AJAX and CSRF protection in Django
Melvin Koh HackerNoon profile picture

Originally published at melvinkoh.me.

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.pyfrom 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 importsclass 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.

Your clap will definitely drive me further. Give me a clap if you like this post.