ashrafZolkopli
Posted on June 15, 2021
In the previous post in this series, I talk about how we can use django-recaptcha to make sure our forms are protected from bot spam submission, however it can be tedious to add them to each form and also does not make it hard to add the recaptcha field if the form are generated by a django package that you had installed previously.
Using the django-honeypot middleware method and knowledge on how django-recaptcha work, I had manage to roll my own middleware that inject the Google Recaptcha field into every page that have a form in a Django weebsite.
1) Start a new django app
We Can first start an django app called G_Recaptha, the name of the app can be anything you want but I'm going with this.
python manage.py startapp G_Recaptha
2) G_Recaptha middleware
We now should add a middleware.py file in G_Recaptha.
in this file, insert the following
import re
from django.utils.safestring import mark_safe
from django.template.loader import render_to_string
from django.conf import settings
from django.utils.encoding import force_str
from .helpers import verify_recaptcha
_POST_FORM_RE = re.compile(
r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE
)
_HTML_TYPES = ("text/html", "application/xhtml+xml")
class BaseRecapthaMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
class RecaptchaRequestMiddleware(BaseRecapthaMiddleware):
def process_view(self, request, callback, callback_args, callback_kwargs):
if request.is_ajax():
return None
return verify_recaptcha(request)
class RecaptchaResponseMiddleware(BaseRecapthaMiddleware):
def __call__(self, request):
response = self.get_response(request)
try:
content_type = response["Content-Type"].split(";")[0]
except (KeyError, AttributeError):
content_type = None
if content_type in _HTML_TYPES:
def add_recaptcha_field(match):
return mark_safe(
match.group()
+ render_to_string(
"G_Recaptcha/RecaptchaV3.html",
{'recaptcha_site_key': settings.RECAPTCHA_PUBLIC_KEY},
)
)
response.content = _POST_FORM_RE.sub(
add_recaptcha_field, force_str(response.content)
)
return response
class RecaptchaMiddleware(
RecaptchaRequestMiddleware,
RecaptchaResponseMiddleware):
pass
this middleware file is almost the exact copy of django-honeypot that have been customize to insert google recaptcha field into and pages in our website that have a form field in the HTML.
3) G_Recaptha helpers.py
In the middleware.py file on line 6 we call for verify_recaptcha from helpers.py file.
Lets now create a file called helpers.py file and insert the following lines into the file:
import requests
from django.conf import settings
from django.http import HttpResponseBadRequest
from django.template.loader import render_to_string
def verify_recaptcha(request):
if request.method == "POST":
recaptcha_response = request.POST.get('g-recaptcha-response')
data = {
'secret': settings.RECAPTCHA_PRIVATE_KEY,
'response': recaptcha_response
}
r = requests.post(
'https://www.google.com/recaptcha/api/siteverify', data=data)
result = r.json()
if result['success'] and \
float(result['score']) > settings.RECAPTCHA_REQUIRED_SCORE:
return None
respond = render_to_string(
'G_Recaptcha/ReCaptcha_error.html',
{}
)
return HttpResponseBadRequest(respond)
4) G_Recaptha templates
In middleware.py file, we force insert a html string into all the pages at line 47. This template is located at
G_Recaptcha/templates/G_Recaptcha/RecaptchaV3.html.
the html for the template are
<script src='https://www.google.com/recaptcha/api.js?render={{recaptcha_site_key}}'></script>
<script>
//global grecaptcha
grecaptcha.ready(function() {
grecaptcha.execute('{{recaptcha_site_key}}', {action: "form"}).then(function(token) {
document.getElementById('g-recaptcha-response').value = token;
});
});
</script>
<input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response">
In helpers.py file we also have a template that responds if the user failed the Google Recaptha test with the template name Recaptcha_error.html located at
G_Recaptcha/templates/G_Recaptcha/Recaptcha_error.html
the template in its barebone is as follow,
<H1>Google ReCaptha Error </H1>
you may want to change the design as required.
5) G_Recaptcha Settings configurations.
lets now open settings.py file and make the following changes as follows
INSTALLED_APPS = [
#...
# Custom Google ReCaptcha
'G_Recaptcha',
#...
]
MIDDLEWARE = [
#...
# Custom Google ReCaptcha
'G_Recaptcha.middleware.RecaptchaMiddleware',
#...
]
# Custom Google Recaptcha
RECAPTCHA_PUBLIC_KEY = config('RECAPTCHA_PUBLIC_KEY')
RECAPTCHA_PRIVATE_KEY = config('RECAPTCHA_PRIVATE_KEY')
RECAPTCHA_REQUIRED_SCORE = config('RECAPTCHA_REQUIRED_SCORE', cast=float)
6)Update .env files
Since we are using python-decouple, we should now update the .env file with the following fields
RECAPTCHA_PUBLIC_KEY = '<site_key>'
RECAPTCHA_PRIVATE_KEY = '<public_key>'
RECAPTCHA_REQUIRED_SCORE = <value between 0.00 to 1.00>
7) Testing
the easiest place to test is the admin login page
as you can see, the google recaptcha had been displayed located at the bottom of such form
now we should test if we failed the google recaptcha by setting the RECAPTCHA_REQUIRED_SCORE = 1.00
in the .env file
as you can see I had failed the recaptcha test.
now lower the RECAPTCHA_REQUIRED_SCORE = 1.00
to the value you are comfortable with such as 0.85
you should now be able to login as normal.
End
We now able to protect all the form in our project with google ReCaptcha. Hopefully this will slowdown or stop any attempt to attack our django webpage.
Posted on June 15, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.