Brute-force attack prevention is a topic that gets mentioned every now and then in the Django community, on mailing-lists and at conferences. I've been thinking about this a little bit and I believe I found a very nice solution.

Note that I'm only tackling the issue of brute-forcing user credentials, not rate-limiting in general.

The default authentication form already accepts a request, it'll store it on self. But we still need to subclass it since it's the form that calls the authentication backend:

fromdjango.contrib.auth.formsimportAuthenticationFormasAuthFormclassAuthenticationForm(AuthForm):defclean(self):username=self.cleaned_data.get('username')password=self.cleaned_data.get('password')ifusernameandpassword:self.user_cache=authenticate(username=username,password=password,request=self.request)ifself.user_cacheisNone:raiseforms.ValidationError(_('Please enter a correct username and password. ''Note that both fields are case-sensitive.'),)elifnotself.user_cache.is_active:raiseforms.ValidationError(_('This account is inactive.'))self.check_for_test_cookie()returnself.cleaned_data

Here we're calling authenticate(username, password, request), the only thing left is to implement the logic on the backend itself. This represents a little bit more code so I'll just link to an implementation, which I've packaged and released under the name of django-ratelimit-backend.

The rate-limiting strategy is borrowed from Simon Willison's ratelimit-cache idea: failed attempts are cached for 5 minutes and if more than X attempts (30 by default) are made, further attempts are blocked until there are less than X attempts in the past 5 minutes.

In order to fully protect your django site, configure your cache backend and go through the checklist (make sure every item is fulfilled).

There is a pretty significant caveat: you can't use Django's default admin site because it's not protected. You need to register your models on ratlimitbackend's admin site instead, and hook that site into your URLconf. This means all apps that automatically register their models (such as django.contrib.auth, django.contrib.sites, django-registration…) won't show up in the admin until you re-register their models on the rate-limited admin site.

This is a bit annoying but I'm happily using this in production and it works wonderfully. The documentation is extensive and it's highly customizable:

failed attempts can be logged to let you take appropriate measures when it gets too annoying,