A pluggable framework for adding two-factor authentication to Django using one-time passwords.
npx @tessl/cli install tessl/pypi-django-otp@1.6.0A pluggable framework for adding two-factor authentication to Django applications using one-time passwords. Django-otp provides a comprehensive solution for implementing multi-factor authentication with support for HOTP (RFC 4226), TOTP (RFC 6238), email-based tokens, and static backup codes.
pip install django-otpimport django_otp
from django_otp import login, verify_token, match_token, devices_for_user, user_has_device, device_classes, DEVICE_ID_SESSION_KEY
from django_otp.util import random_hex, random_number_token, hex_validator
from django_otp.qr import write_qrcode_imageFor specific plugins:
from django_otp.plugins.otp_totp.models import TOTPDevice
from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
from django_otp.plugins.otp_email.models import EmailDevice
from django_otp.plugins.otp_hotp.models import HOTPDeviceFor forms and views:
from django_otp.forms import OTPAuthenticationForm, OTPTokenForm
from django_otp.views import LoginView
from django_otp.decorators import otp_required
from django_otp.middleware import OTPMiddleware, is_verifiedfrom django_otp import devices_for_user, user_has_device
from django_otp.plugins.otp_totp.models import TOTPDevice
from django.contrib.auth.models import User
# Check if user has any OTP devices
user = User.objects.get(username='alice')
has_device = user_has_device(user)
# Get all devices for a user
devices = list(devices_for_user(user))
# Create a TOTP device for a user
device = TOTPDevice.objects.create(
user=user,
name='My Authenticator',
confirmed=True
)
# Generate a QR code URL for device setup
config_url = device.config_urlfrom django_otp import verify_token
# Verify a token against a specific device
device = verify_token(user, device.persistent_id, '123456')
if device:
print(f"Token verified with device: {device.name}")
else:
print("Token verification failed")from django_otp.decorators import otp_required
from django.shortcuts import render
@otp_required
def secure_view(request):
# This view requires OTP verification
return render(request, 'secure_page.html')
# Check if user is OTP verified
from django_otp.middleware import is_verified
def my_view(request):
if is_verified(request.user):
# User is verified with OTP
passDjango-otp is built around a plugin architecture with several key components:
Device model and common functionalityThe Device abstract model serves as the foundation, with concrete implementations provided by plugins. Each device can generate challenges and verify tokens, with built-in security features like rate limiting and cooldown periods.
Core functions for managing OTP authentication, including user login, token verification, and device management. These functions form the foundation of django-otp's authentication system.
def login(request, device):
"""
Persist the given OTP device in the current session.
Args:
request: The HTTP request
device: The OTP device used to verify the user
"""
def verify_token(user, device_id, token):
"""
Attempts to verify a token against a specific device.
Args:
user: The user supplying the token
device_id: A device's persistent_id value
token: An OTP token to verify
Returns:
The device that accepted token, if any, or None
"""
def match_token(user, token):
"""
Attempts to verify a token on every device attached to the user.
Warning: Use of this function is no longer recommended.
Args:
user: The user supplying the token
token: An OTP token to verify
Returns:
The device that accepted token, if any, or None
"""
def devices_for_user(user, confirmed=True, for_verify=False):
"""
Return an iterable of all devices registered to the given user.
Args:
user: Standard or custom user object
confirmed: If None, all devices returned; otherwise filter by confirmed status
for_verify: If True, load devices with select_for_update
Returns:
Iterable of Device objects
"""
def user_has_device(user, confirmed=True):
"""
Return True if the user has at least one device.
Args:
user: Standard or custom user object
confirmed: If None, all devices considered; otherwise filter by confirmed status
Returns:
Boolean indicating if user has devices
"""
def device_classes():
"""
Returns an iterable of all loaded device models.
Returns:
Iterable of Device model classes
"""Abstract base models and mixins that provide the foundation for all OTP devices, including security features like throttling and cooldowns.
class Device(models.Model): ...
class SideChannelDevice(Device): ...
class CooldownMixin: ...
class ThrottlingMixin: ...Time-based one-time passwords following RFC 6238, typically used with authenticator apps like Google Authenticator or Authy.
class TOTPDevice(TimestampMixin, ThrottlingMixin, Device):
key = models.CharField(max_length=80)
step = models.PositiveSmallIntegerField(default=30)
t0 = models.BigIntegerField(default=0)
digits = models.PositiveSmallIntegerField(choices=[(6, 6), (8, 8)], default=6)
tolerance = models.PositiveSmallIntegerField(default=1)
drift = models.SmallIntegerField(default=0)
last_t = models.BigIntegerField(default=-1)HMAC-based one-time passwords following RFC 4226, using a counter that increments with each use.
class HOTPDevice(TimestampMixin, ThrottlingMixin, Device):
key = models.CharField(max_length=80)
digits = models.PositiveSmallIntegerField(choices=[(6, 6), (8, 8)], default=6)
tolerance = models.PositiveSmallIntegerField(default=5)
counter = models.BigIntegerField(default=0)One-time passwords delivered via email, useful for users who don't have smartphone access to authenticator apps.
class EmailDevice(TimestampMixin, CooldownMixin, ThrottlingMixin, SideChannelDevice):
email = models.EmailField(blank=True)Pre-generated backup codes for emergency access when primary OTP devices are unavailable.
class StaticDevice(TimestampMixin, ThrottlingMixin, Device): ...
class StaticToken(models.Model):
device = models.ForeignKey(StaticDevice, on_delete=models.CASCADE)
token = models.CharField(max_length=16)Forms, views, middleware, and decorators for integrating OTP authentication into Django applications.
class OTPAuthenticationForm(OTPAuthenticationFormMixin, AuthenticationForm): ...
class OTPTokenForm(OTPAuthenticationFormMixin, forms.Form): ...
class LoginView(auth_views.LoginView): ...
def otp_required(view=None, redirect_field_name='next', login_url=None, if_configured=False): ...
class OTPMiddleware: ...Admin classes and forms for managing OTP devices through Django's admin interface.
class OTPAdminSite(admin.AdminSite): ...
class OTPAdminAuthenticationForm(AdminAuthenticationForm, OTPAuthenticationFormMixin): ...Low-level implementations of HOTP and TOTP algorithms for direct use or custom device implementations.
def hotp(key, counter, digits=6): ...
def totp(key, step=30, t0=0, digits=6, drift=0): ...
class TOTP: ...Utility for generating QR codes for device setup, supporting both qrcode and segno libraries.
def write_qrcode_image(data, out):
"""
Write a QR code image for data to out in SVG format.
Args:
data: Data to encode in QR code
out: Output stream to write SVG image
Raises:
ModuleNotFoundError: If neither qrcode nor segno is available
"""Helper functions for generating secure tokens and validating hex-encoded data.
def random_hex(length: int = 20) -> str:
"""
Returns a string of random bytes encoded as hex.
Args:
length: The number of (decoded) bytes to return
Returns:
A string of hex digits
"""
def random_number_token(length: int = 6) -> str:
"""
Returns a string of random digits.
Args:
length: The number of digits to return
Returns:
A string of decimal digits
"""
def hex_validator(length: int = 0):
"""
Returns a function to validate hex-encoded CharField values.
Args:
length: If > 0, validation fails unless decoded value is exactly this many bytes
Returns:
Validator function for use in model fields
"""Utility functions for checking OTP verification status.
def is_verified(user) -> bool:
"""
Check if a user has been verified with OTP.
Args:
user: User object to check
Returns:
True if user.otp_device is not None
"""DEVICE_ID_SESSION_KEY = 'otp_device_id' # Session key for storing device ID# Device verification states
class GenerateNotAllowed(enum.Enum):
"""Enum for reasons why token generation is not allowed."""
COOLDOWN_DURATION_PENDING = 'COOLDOWN_DURATION_PENDING'
class VerifyNotAllowed(enum.Enum):
"""Enum for reasons why token verification is not allowed."""
N_FAILED_ATTEMPTS = 'N_FAILED_ATTEMPTS'
# Settings wrapper
class Settings:
"""Settings object for django-otp configuration."""
def __getattr__(self, name): ...
# Device manager
class DeviceManager(models.Manager):
"""Manager for Device models."""
def devices_for_user(self, user, confirmed=None): ...
# Model mixins
class CooldownMixin(models.Model):
"""Mixin that provides cooldown functionality for devices."""
class Meta:
abstract = True
class ThrottlingMixin(models.Model):
"""Mixin that provides throttling functionality for devices."""
class Meta:
abstract = True
class TimestampMixin(models.Model):
"""Mixin that provides timestamp tracking for devices."""
class Meta:
abstract = True