django-axes

Track failed login attempts and lock out brute-force attackers on Django-powered sites.

Library
PyPI
v8.3.1
1,699stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
81/100Excellent
Development Activity84
Maintenance72
Community80
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
81/100Excellent
Architecture88
Code Quality85
Innovation62
Learning Curve90

django-axes is a Django plugin that monitors login attempts across a site and blocks further attempts once a configured failure threshold is exceeded. It hooks into Django’s authentication signals so it works with the standard django.contrib.auth login flow, Django REST Framework, and most third-party auth backends without requiring changes to view code.

Attempts can be tracked by IP address, username, user agent, or combinations of these, and persisted either to the database (for a durable audit trail) or to Django’s cache framework (for a fast, DDoS-resistant path that avoids hitting the database on every failed request). Cool-off periods, permanent lockouts, IP allow/block lists, and username allow lists give operators fine-grained control over how aggressively lockouts are enforced.

Maintained under the Jazzband collective, the project has been actively developed since 2009 and is a common companion to Django’s built-in auth system for teams that need brute-force protection without adopting a full external WAF or rate-limiting service.

What You Get

  • An authentication backend (AxesBackend/AxesStandaloneBackend) that blocks locked-out users before Django’s normal auth backends run
  • A middleware (AxesMiddleware) that turns lockout signals into readable HTTP 403 responses, including Django REST Framework requests
  • Two interchangeable handler implementations — AxesDatabaseHandler for a persisted, queryable attempt log and AxesCacheHandler for low-overhead cache-backed tracking
  • Configurable lockout parameters (IP address, username, user agent, or combinations), cool-off periods, and permanent lockout support
  • IP address and username allow/block lists, plus per-view and per-callable whitelisting decorators
  • A Django admin integration for reviewing and resetting recorded access attempts

Common Use Cases

  • Adding brute-force login protection to a Django site’s default django.contrib.auth login view with minimal configuration
  • Rate-limiting failed login attempts by IP address behind a load balancer or reverse proxy where forwarded headers need custom handling
  • Protecting a Django REST Framework token or session login endpoint from credential-stuffing attacks via the bundled middleware
  • Auditing suspicious login activity through the database-backed handler and Django admin, including per-user and per-IP failure history
  • Combining username- and IP-based lockout rules to prevent both single-account brute forcing and distributed credential-stuffing sweeps

Under The Hood

Architecture Execution starts at Django’s authentication signals: AxesStandaloneBackend.authenticate() (axes/backends.py) is placed first in AUTHENTICATION_BACKENDS and consults AxesProxyHandler.is_allowed() before Django’s normal backends run, raising AxesBackendPermissionDenied to short-circuit login for locked-out clients. AxesProxyHandler (axes/handlers/proxy.py) is a thin dispatcher that lazily imports and memoizes whichever concrete handler is named in settings.AXES_HANDLER, so the actual attempt-tracking logic is fully swappable. Two implementations ship: AxesDatabaseHandler (axes/handlers/database.py), which persists attempts to AccessAttempt/AccessLog/AccessFailureLog models via the ORM, and AxesCacheHandler (axes/handlers/cache.py), which stores counters directly in Django’s cache backend for lower latency. Both implementations share a common contract defined by the AbstractAxesHandler ABC and AxesBaseHandler mixin (axes/handlers/base.py), so lockout decisions, signal wiring, and cool-off logic stay identical regardless of storage backend. AxesMiddleware (axes/middleware.py) closes the loop by converting lockout state into an HTTP 403 response after the request completes, with explicit async/sync dual support via asgiref. This handler-behind-a-proxy design means the storage strategy is a deployment-time config choice, not a code change.

Tech Stack The library targets Django 4.2 through the in-development 6.0/main branches (see the tox matrix in pyproject.toml) and Python 3.10-3.14, with django>=4.2 and asgiref>=3.6.0 as its only hard runtime dependencies; django-ipware>=3 is an optional extra for smarter client-IP resolution behind proxies. It ships no web framework, ORM, or CLI of its own — it plugs entirely into Django’s existing auth signal system, admin site, and ORM via standard Django migrations (axes/migrations/). Packaging uses setuptools with setuptools_scm for version derivation from git tags, and the project is distributed as a conventional PyPI sdist/wheel.

Code Quality The project has 15 dedicated test modules under tests/ (test_backends.py, test_handlers.py, test_middleware.py, test_models.py, test_login.py, and others) covering the backend, both handler implementations, middleware, signals, and Django admin integration, run via pytest with pytest-django and coverage reporting (branch coverage tracked and uploaded to Codecov). Type checking is enforced with mypy (mypy.ini), static analysis with prospector, and formatting with black — all wired into the tox djqa environment and CI. Error paths are explicit: the code raises dedicated exception types (AxesBackendPermissionDenied, AxesBackendRequestParameterRequired) rather than swallowing failures, and the handler ABC forces subclasses to implement every signal-handling method or fail loudly at instantiation.

What Makes It Unique Rather than bolting brute-force protection on as separate middleware that duplicates Django’s auth logic, django-axes integrates directly with Django’s user_login_failed/user_logged_in/user_logged_out signals and its AUTHENTICATION_BACKENDS chain, so it observes the exact same login flow every other backend sees without requiring view-level changes. The pluggable handler abstraction — letting an operator swap between a persisted database audit trail and a fast cache-only counter with a single settings change — is unusual among Django brute-force packages, most of which hard-code one storage strategy.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search