django-otp

A pluggable framework for adding two-factor authentication to Django using one-time passwords.

Library
PyPI
v1.7.2
631stars
Unlicense

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
60/100Good
Development Activity64
Maintenance20
Community68
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
83/100Excellent
Architecture85
Code Quality78
Innovation82
Learning Curve85

django-otp adds one-time-password-based two-factor authentication to a Django project without dictating how the login flow itself is built. It integrates with django.contrib.auth through a middleware that populates request.user.otp_device and request.user.is_verified(), and defines an abstract Device model that any OTP mechanism can subclass to plug into the same verification, throttling, and session-binding pipeline.

Four ready-to-use plugins ship in the box: otp_static for backup/recovery codes, otp_hotp and otp_totp implementing the standard RFC 4226/6238 counter- and time-based algorithms, and otp_email for sending a token to a user’s registered address. The project is stable and widely deployed but, per its own README, is no longer under heavy active development by its maintainer, so contributions are welcomed but the design is considered largely settled.

What You Get

  • OTPMiddleware that populates request.user.otp_device and installs user.is_verified(), with async-capable acall support
  • An abstract Device / ThrottlingMixin base for building custom OTP plugins that reuse the built-in verification and throttling pipeline
  • Four bundled plugins: otp_static (backup codes), otp_hotp, otp_totp (RFC 4226/6238 algorithms included), and otp_email
  • An otp_required view decorator that mirrors Django’s login_required, plus admin integration for managing per-user devices
  • django_otp.login()/verify_token()/match_token()/devices_for_user() helper functions wrapped in transaction.atomic() for safe concurrent verification

Common Use Cases

  • Enforcing 2FA on an existing django.contrib.auth login flow with OTPMiddleware and otp_required
  • Building a custom OTP method (push notification, hardware token) by subclassing the abstract Device model
  • Offering backup/recovery codes via otp_static for users locked out of their primary authenticator
  • Giving users an email-based fallback second factor with otp_email when SMS isn’t an option

Under The Hood

Architecture django-otp is built around an abstract Device model (models.py) that plugins subclass; the middleware (middleware.py) is the request-time entry point, wrapping request.user in a SimpleLazyObject and delegating to device_classes() in __init__.py, which introspects every installed Django app’s models at runtime via apps.get_app_configs() rather than hard-coding concrete device types. Verification flows through devices_for_user(), verify_token(), and match_token(), all wrapped in transaction.atomic() with optional select_for_update() to serialize concurrent verification attempts against the same device. The four bundled plugins under django_otp/plugins/ are each self-contained Django apps (own models.py, migrations, tests.py) that depend only on the core Device/ThrottlingMixin base classes, so adding a new OTP mechanism means writing an independent app rather than touching core code.

Tech Stack The package is built and released with hatch, declares a single runtime dependency (django >= 4.2), and offers segno/qrcode as optional extras for QR-code rendering in the TOTP plugin. Its async story piggybacks on Django’s own asgiref.sync primitives rather than a separate async framework, and its hatch-driven test matrix runs against multiple Django versions (5.2/6.0/6.1) and, optionally, PostgreSQL for concurrency-sensitive tests. There is no separate service or frontend build step — it installs directly into an existing Django project’s INSTALLED_APPS and middleware stack.

Code Quality Every plugin and the core package carry a tests.py, run through Django’s own test runner (python -m django test) with coverage tracked via hatch scripts, and linting is enforced through flake8, isort, and black in a hatch run check pipeline. Type hints are present on newer helper functions (e.g. _normalize_persistent_id(persistent_id: str) -> str) but aren’t applied comprehensively across the codebase. Docstrings are extensive and written in reST/Sphinx conventions, feeding an autodoc-generated reference on Read the Docs; exception handling is deliberate rather than incidental, e.g. targeted use of contextlib.suppress.

API Design The public surface is a small set of plain functions and one decorator: django_otp.login(), verify_token(), match_token(), devices_for_user(), and otp_required, the last deliberately mirroring Django’s own login_required so existing Django developers need to learn almost nothing new. Getting started is just adding the middleware after AuthenticationMiddleware and installing a plugin app, and the full API is documented with Sphinx autodoc plus a stable hosted reference, keeping the learning curve close to that of Django’s built-in auth system.

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