2

I am trying to implement a server-side check to prevent users from double-submitting my forms (Django web app).

One technique I'm trying is:

1) When the form is created, save a unique ID in the session, plus pass the unique ID value into the template as well.

2) When the form is submitted, pop the unique ID from the session, and compare it to the same unique ID retrieved from the form.

3) If the values are the same, allow processing, otherwise not.

These SO answers contributed in me formulating this.

Here's a quick look at my generalized code:

def my_view(request):
    if request.method == 'POST':
        secret_key_from_form = request.POST.get('sk','0')
        secret_key_from_session = request.session.pop('secret_key','1')
        if secret_key_from_form != secret_key_from_session:
            return render(request,"404.html",{})
        else:
            # process the form normally
            form = MyForm(request.POST,request.FILES)                
            if form.is_valid():
                # do something
            else:
                # do something else
    else:
        f = MyForm()
        secret_key = uuid.uuid4()
        request.session["secret_key"] = secret_key
        request.session.modified = True 
        return render(request,"my_form.html",{'form':f,'sk':secret_key})

And here's a sample template:

<form action="{% url 'my_view' %}" method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    <input type="hidden" name="sk" value="{{ sk }}">
    {{ form.my_data }}
    <button type="submit">OK</button>
</form>

This set up has failed to stop double-submission.

I.e., one can go on a click frenzy and still end up submitting tons of copies. Moreover, if I print secret_key_from_form and secret_key_from_session, I see them being printed multiple times, even though secret_key_from_session should have popped after the first attempt.

What doesn't this work? And how do I fix it?


UPDATE: when I use redis cache to save the value of the special key, this arrangement works perfectly. Therefore, it seems the culprit is me being unable to update request.session values (even with trying request.session.modified=True). I'm open to suggestions vis-a-vis what could be going wrong.


Note that this question specifically deals with a server-side solution to double-submission - my JS measures are separate and exclusive to this question.

Hassan Baig
  • 15,055
  • 27
  • 102
  • 205

2 Answers2

0

You might just need request.session.modified = True. If you want to make sure that the session is deleting you can use del too.

secret_key_from_session = request.session.get('secret_key','1')
del request.session['secret_key']
request.session.modified = True
A. J. Parr
  • 7,731
  • 2
  • 31
  • 46
  • Yea, I thought about that, but one doesn't need `modified = True` after `pop`, no? – Hassan Baig Jul 20 '17 at 14:13
  • I think `pop` is modifying the session, it needs to commit that change server-side when it sends the response. – A. J. Parr Jul 20 '17 at 14:15
  • Well for what it's worth, I tried this change in `my_view`, but to no avail. Exactly the same behavior persists. This isn't the answer for sure. Perhaps we're looking at this problem from the wrong angle. – Hassan Baig Jul 20 '17 at 14:51
  • I had actually pulled my answer partially from https://stackoverflow.com/a/9781233/784648 which is the accepted answer for "How do I delete a session key in Django after it is used once?" There must be something else wrong with code if this isn't working. – A. J. Parr Jul 20 '17 at 23:22
  • Could be. In fact, look at the latest update I posted underneath the question. – Hassan Baig Jul 21 '17 at 08:31
0

I couldn't figure out what caused the problem, but via substituting Redis cache for every request.session call, I was able to get my desired results. I'm open to suggestions.

Hassan Baig
  • 15,055
  • 27
  • 102
  • 205