Integrating Captchas with Django

Why do I need CAPTCHAs?

As soon as your web project becomes bigger you will notice that bots are trying to spam your blog and to fake user inputs. When I created a blog with Django for a website with about 60.000 unique users per day I had to delete about 20 spam blog entries every single day. Pretty annoying. So I had to find a way to ensure that the response is not done by a computer.

How do CAPTCHAs work?

Captchas ask the user to complete a simple test like typing in a word displayed in an image. That’s the most common and useful method. But soon you will see that not all captchas are equal in security. People developed technologies to read several types of Captchas.

Why reCAPTCHA is the best Captcha

In their own words:

“reCAPTCHA is a free CAPTCHA service that helps to digitize books, newspapers and old time radio shows.”

That means that the words displayed in the captcha image are extracted from digital photos which makes it really hard to read for computers (and sometimes for humans ;-). Additionally reCAPTCHA warps the image and distorts it by adding random lines which makes it even harder to read for bots.

How to integrate reCAPTCHA with Django

There are two ways to implement reCAPTCHAs into a Django application. One way is to use their reCAPTCHA client for python but that didn’t work for me. So I read their API documentation and found out that it’s so simple that you can implement it with just a few lines of Python code.

At first you will need to sign up for the reCAPTCHA project. You can do that at http://recaptcha.net/whyrecaptcha.html . Follow the instructions and after a few clicks you will get your private key and your public key. You will need the public key to create the form elements for reCAPTCHA and the private key is needed for the server request.

The frontend

At reCAPTCHA.net you will get a JavaScript code that will look like that:

html<noscript>
  <iframe src="http://api.recaptcha.net/noscript?k=your_public_key" mce_src="http://api.recaptcha.net/noscript?k=your_public_key" height="300" width="500" frameborder="0"></iframe>
  <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
  <input type="hidden" name="recaptcha_response_field" value="manual_challenge">
</noscript>

You can change the theme if you want, available themes are red, white, blackglass, clean and custom.

The backend

Alright, when the user types in the captcha and send the form we will need to ask reCAPTCHA whether the typed words are right (or approximately right, I noticed that small typos are disregarded). For that we will need httplib and urllib to do an HTTP request.

pythonimport urllib, httplib
from django.http import HttpResponse

def create(request):
  params = dict(privatekey='YOUR\_PRIVATE\_KEY', remoteip=request.META.get("REMOTE\_ADDR"), challenge=request.POST.get("recaptcha\_challenge\_field", None), response=request.POST.get("recaptcha\_response_field", None))
  params = urllib.urlencode(params)
  headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
  conn = httplib.HTTPConnection("api-verify.recaptcha.net")
  conn.request("POST", "/verify", params, headers)
  response = conn.getresponse()

  if response.status == 200:
    data = response.read()
  else:
    data = ""

  conn.close()

Okay, we did the request and put the response to “data”. Let’s see what api-verify.recaptcha.net will respond:

true

Everything is fine, one line that says that the request is okay.

false
incorrect-captcha-sol

We have to lines. The first one says that the request went wrong and the second line gives the reason for the failure. In this case it’s incorrect-captcha-sol which means “Incorrect captcha solution”. You can find all error codes in their API Documentation (http://recaptcha.net/apidocs/captcha/client.html)

Now let’s tell django to check whether the request went fine:

pythonresult = data.startswith('true')

if not result:
  bits = data.split('\n', 2)
  error_code = ""

  if len(bits) > 1:
    error_code = bits[1]

  return HttpResponse("Oh noez, verification failed with error code: " + error_code)
else:
  return HttpResponse("Hell yea, verification successful.")

And that’s it :) Have fun.