Binaroid's Blog

Reducing confirmation spam incidents with Google reCAPTCHA and django-allauth

Peter Marcely - Feb 05, 2020

For the Working Nomads project, django-allauth is used to simplify the authentication and user management process. An email confirmation feature is also used to ensure all emails of our users are valid. 

Recently, I have noticed an increased number of flagged spam email messages which are sent during the registration process from AWS SES. This, of course, is worsening our spam reputation and could potentially lead to being banned from sending emails with our current provider. Even though, I don't understand what is the purpose of these spammers, one of the reasonable ways how to solve this issue is Google reCAPTCHA.

There seems to be a simple solution how to integrate reCAPTCHA and django-allauth, but I wanted to be able to monitor these spam attempts, so the decision was to integrate a 'vanilla' reCAPTCHA which is not that hard after all, since django-allauth offers a range of customizing options.

 

Implementation

views.py

class SignupFormCaptcha(SignupForm):
    recaptcha_token = forms.CharField(required=True)

    def clean(self):
        super().clean()

        url = 'https://www.google.com/recaptcha/api/siteverify'
        params = {
            'secret': settings.RECAPTCHA_PRIVATE_KEY,
            'response': self.cleaned_data['recaptcha_token']
        }

        response = requests.post(url, params)
        if response.status_code == requests.codes.ok:
            if response.json()['success'] and response.json()['action'] == 'signup':
                _logger.debug('Captcha valid for user={}'.format(self.cleaned_data.get('email')))
            else:
                _logger.debug('Captcha invalid for user={}'.format(self.cleaned_data.get('email')))
                raise forms.ValidationError('ReCAPTCHA is invalid.')
        else:
            _logger.error('Cannot validate reCAPTCHA for user={}'.format(self.cleaned_data.get('email')))

        return self.cleaned_data

signup template

<script src='https://www.google.com/recaptcha/api.js?render=your_widget_key'></script>
<script>
    grecaptcha.ready(function() {
        grecaptcha.execute('your_widget_key', {action: 'signup'}).then(
            function(token) {
                $('#signup_form').prepend('<input type="hidden" name="recaptcha_token" value="' + token + '">');
        });
    });
</script>

settings.py

ACCOUNT_FORMS = {'signup': 'users.forms.SignupFormCaptcha'}
RECAPTCHA_PUBLIC_KEY = 'Your public key'
RECAPTCHA_PRIVATE_KEY = 'Your private key'

 

Results

After implementing the changes, the complaint rate went down to 0.02% from approx 0.1%.

The number of new users for a certain period with valid captcha was 2820 while those with invalid captcha were 421. That means around 13% of registrations were made by bots and people who cannot pass reCAPTCHA. 

The false-positive number is probably quite high, but on the other side, it has fixed our issue and we don't need to worry to be banned from Amazon SES. If you know a different solution feel free to share it with us.