Invisible reCAPTCHA v2 with Django and Zero
Protecting your web application from bots is necessary to prevent spam and other forms of abuse. This article provides a step-by-step guide to implementing this type of protection with reCAPTCHA.
Sam Magura
The unfortunate reality of web application development is that we cannot only think about how our application will be used — we must also think about how it will be misused. This article focuses on preventing automated abuse by bots through the use of captchas. A captcha is essentially a Turing test , meaning that it allows a web application to differentiate between legitimate use by a human and illegitimate use by a bot.
We will be using Google's reCAPTCHA, the most widely used captcha library on the web. If you have ever been asked to check the "I am not a robot" box, then you've used reCAPTCHA. Though, the "I'm not a robot checkbox" is just one flavor of reCAPTCHA — there are several options to choose from:
The rest of this article provides a step-by-step guide to setting up invisible reCAPTCHA v2 in a Django web application. Invisible reCAPTCHA v2 is a great option because it requires no user input. This flavor of reCAPTCHA is also easy to use, since it indicates whether or not the user is a bot via a simple yes/no response, rather than a confidence score like reCAPTCHA v3.
Calling the reCAPTCHA API from your backend requires a secret key. We'll store this key in the Zero secrets manager and fetch it at runtime using the Zero Python SDK .
🔗 The full code for this example is available in the zerosecrets/examples GitHub repository.
Secure your secrets conveniently
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.
Overview of Integrating with reCAPTCHA v2
Adding invisible reCAPTCHA v2 to your application is a straightforward task, though it does require code on both the frontend and backend. If you get stuck or want to learn about the full range of use cases supported by the library, check out the official docs provided by Google.
At a high level, here is how the process works. All steps occur client side, except for the last one which occurs server side.
- Your web application displays a form to the user, e.g. a form to sign up as a new user.
- The reCAPTCHA JavaScript runs in the background, analyzing the user's behavior to determine if they are a bot.
- The user submits the form, which does not submit the form to the server, but instead triggers the reCAPTCHA challenge.
- reCAPTCHA performs the challenge and invokes your callback function.
- Your callback function submits the form for real. The data posted to the server includes the reCAPTCHA response string.
- Your backend calls the reCAPTCHA API with your secret key and the response from the frontend. The API returns a
success
boolean that tells you if the user passed the challenge.
Signing Up for reCAPTCHA
You can sign up for reCAPTCHA and get your API key here . Select "Select reCAPTCHA v2 - Invisible reCAPTCHA badge" and add localhost
as a domain. You'll be given your site key and secret key once you complete the signup flow. The site key can be shared publicly, while the secret key must be kept private.
We're using Zero to manage our secrets, so log in to Zero and create new project to store the reCAPTCHA keys. While we only need to fetch the secret key from Zero (since the site key can be committed directly to your git repository), it's convenient to save both keys in Zero. This makes Zero the ultimate source of truth for all of the credentials needed to run your project.
Setting Up the Django Project
If you already have a working Django project, feel free to skip this section. We can roughly follow the official Django tutorial to get up and running with a working app. The main difference is that we'll use Poetry so that the dependencies of our new app don't get intermingled with any Python packages that are installed globally. We also don't need to set up a database or data model for this exercise.
Here's a summary of the steps to follow:
-
Make a new
recaptcha-django
directory andcd
into it. -
Run
poetry init
and enter "no" when asked if you want to define dependencies interactively. -
poetry add Django
-
poetry run django-admin startproject recaptcha_django .
-
Create a new Django app called
pages
viapoetry run python manage.py startapp pages
. -
Add
'pages.apps.PagesConfig'
to the list ofINSTALLED_APPS
insettings.py
. -
Configure the project to forward all requests to the
pages
app by settingurls.py
to:pages/urls.pyfrom django.urls import path, include urlpatterns = [ path('', include('pages.urls')), ]
Then, add a simple "Hello World" view and template to the pages
app.
from django.shortcuts import render
def index(request):
context = {}
return render(request, 'pages/index.html', context)
pages/templates/pages/index.html
should be a valid HTML file which displays a simple user registration form:
<form id="demo-form" action="/" method="POST">
{% csrf_token %}
<label for="username">Username</label>
<input id="username" name="username" />
<br />
<button>Sign up</button>
</form>
Run the Django development server with poetry run python manage.py runserver
, navigate to http://localhost:8000
, and you should see the signup form.
💡 You must access the site via
localhost
rather than127.0.0.1
since we providedlocalhost
as our domain when signing up for reCAPTCHA.
Frontend Integration
The easiest way to set up reCAPTCHA v2 on the frontend is to have the library automatically bind the challenge to a <button>
element. You can do this by pasting in the required <script>
tag and adding a few data attributes to the form's submit button:
<head>
<!-- ... -->
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<button class="g-recaptcha" data-sitekey="YOUR-RECAPTCHA-SITE-KEY-HERE" data-callback="onSubmit">Sign up</button>
After reCAPTCHA performs the challenge, it will invoke the function specified in data-callback
. This function must be defined in the global scope. All the callback function needs to do is submit the form to the backend, like so:
function onSubmit() {
document.getElementById('demo-form').submit()
}
The reCAPTCHA response string will automatically be included in the form's POST data.
💡 "Invisible" reCAPTCHA v2 is not truly invisible, since a reCAPTCHA badge will be shown in the bottom right of the page. You are allowed to hide the badge, but you must display a message that links to Google's Privacy Policy and Terms of Service. This is described here in the reCAPTCHA FAQ.
Backend Integration
Now we need to modify the backend logic to pass the reCAPTCHA response from the form to the reCAPTCHA web API. The API will then return a JSON response which indicates whether the user is a bot.
This is straightforward to do: you can access the form's POST data via the request.POST
dictionary, and then make the API call using the requests
package. The finished code looks like this:
def index(request):
context = {}
if request.method == 'POST':
username = request.POST['username']
recaptcha_response = request.POST['g-recaptcha-response']
# TODO Implement fetch_recaptcha_secret_key, which gets the key from Zero
secret_key = fetch_recaptcha_secret_key()
r = requests.post(
'https://www.google.com/recaptcha/api/siteverify',
data={
'secret': secret_key,
'response': recaptcha_response
}
)
google_response = r.json()
if google_response['success']:
context['message'] = (
'reCAPTCHA challenge passed. ' +
'User "{}" created successfully.'.format(username)
)
else:
context['message'] = 'reCAPTCHA challenge failed.'
if 'error-codes' in google_response:
error_codes = ', '.join(google_response['error-codes'])
context['message'] += ' Error codes: {}'.format(error_codes)
return render(request, 'pages/index.html', context)
context['message']
can be then be displayed by the index.html
template.
The final step is to fetch the reCAPTCHA v2 secret key from Zero. This is a breeze thanks to the Zero Python SDK , which you can install via
poetry add zero-sdk
Then all we have to do is read the ZERO_TOKEN
environment variable and pass it to the SDK. Once the secret key has been retrieved, we'll store it in a module-level variable so that it does not need to be refetched every time the new user form is submitted.
import os
from zero_sdk import zero
secret_key = None
def fetch_recaptcha_secret_key():
global secret_key
if secret_key is not None:
return secret_key
ZERO_TOKEN = os.getenv('ZERO_TOKEN')
if ZERO_TOKEN is None:
raise Exception('ZERO_TOKEN environment variable not set.')
secrets = zero(token=ZERO_TOKEN, pick=['recaptcha']).fetch()
if 'recaptcha' not in secrets:
raise Exception('recaptcha secret not found.')
if 'secret_key' not in secrets['recaptcha']:
raise Exception('secret_key field not found.')
secret_key = secrets['recaptcha']['secret_key']
if len(secret_key) == 0:
raise Exception('secret_key field is empty.')
return secret_key
The finished app can be run with
ZERO_TOKEN='YOUR-ZERO-TOKEN' poetry run python manage.py runserver
Conclusion
If you followed along with the post, you now have a user signup form that is protected from bots! This will prevent attackers from using automation to create bogus user accounts on your platform. While we used user signup in this example, it's a good idea to add reCAPTCHA integration to any publicly-accessible form that might be abused.
This article showcased how Zero can serve as the single source of truth for your project's secrets. The fact that the reCAPTCHA API key is fetched from Zero at runtime makes it easier to integrate your Django app with additional APIs like Stripe, Twilio, .etc in the future. It's also simpler to rotate your API keys on a regular basis when using Zero since the application's configuration does not need to be updated.
Happy coding!
Other articles
Secrets Usage History: What it is and why it matters
If a secret is obtained by a malicious actor, the consequences can be severe. Monitoring the usage history of a secret in Zero allows you to detect unauthorized access and act before the secret is used in an exploit.
Using Notion as a Human-Readable Database
Capture form submissions from your web app and store them where your team works.
Secure your secrets
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.