A pluggable framework for adding two-factor authentication to Django using one-time passwords.
—
Time-based one-time passwords (TOTP) following RFC 6238, typically used with authenticator apps like Google Authenticator, Authy, or 1Password. TOTP generates tokens based on the current time and a shared secret key.
The main model for TOTP-based OTP devices with built-in security features and drift compensation.
class TOTPDevice(TimestampMixin, ThrottlingMixin, Device):
"""
Time-based OTP device following RFC 6238.
Fields:
- key: CharField(max_length=80) - Hex-encoded secret key
- step: PositiveSmallIntegerField(default=30) - Time step in seconds
- t0: BigIntegerField(default=0) - Unix time to start counting
- digits: PositiveSmallIntegerField - Number of digits (6 or 8)
- tolerance: PositiveSmallIntegerField(default=1) - Time step tolerance
- drift: SmallIntegerField(default=0) - Clock drift compensation
- last_t: BigIntegerField(default=-1) - Last verified time step
"""
key = models.CharField(max_length=80, validators=[hex_validator()])
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)
@property
def bin_key(self) -> bytes:
"""Secret key as binary string."""
@property
def config_url(self) -> str:
"""Configuration URL for QR codes and authenticator apps."""
def verify_token(self, token) -> bool:
"""
Verify TOTP token with drift compensation.
Parameters:
- token: str - Token to verify
Returns:
bool - True if token is valid
"""
def get_throttle_factor(self) -> int:
"""Return throttle factor from settings."""Admin interface for managing TOTP devices with QR code generation support.
class TOTPDeviceAdmin(admin.ModelAdmin):
"""Admin interface for TOTPDevice with QR code support."""
def qrcode_link(self, device):
"""Generate QR code link for device configuration."""
def config_view(self, request, pk):
"""Configuration view showing device setup information."""
def qrcode_view(self, request, pk):
"""QR code image view for device setup."""from django_otp.plugins.otp_totp.models import TOTPDevice
from django.contrib.auth.models import User
# Create a basic TOTP device
user = User.objects.get(username='alice')
device = TOTPDevice.objects.create(
user=user,
name='My Phone Authenticator',
confirmed=False # Set to True after user confirms setup
)
# Create device with custom settings
device = TOTPDevice.objects.create(
user=user,
name='High Security Device',
digits=8, # 8-digit tokens instead of 6
step=60, # 60-second time step instead of 30
tolerance=2 # Allow 2 time steps of tolerance
)
print(f"Device key: {device.key}")
print(f"Setup URL: {device.config_url}")from django_otp.qr import write_qrcode_image
from io import BytesIO
def generate_qr_code_for_device(device):
"""Generate QR code image for TOTP device setup."""
# Create QR code data
qr_data = device.config_url
# Generate QR code image
img_buffer = BytesIO()
write_qrcode_image(qr_data, img_buffer)
img_buffer.seek(0)
return img_buffer.getvalue()
# Usage
device = TOTPDevice.objects.get(pk=1)
qr_image_data = generate_qr_code_for_device(device)
# Save to file or return in HTTP response
with open('qr_code.png', 'wb') as f:
f.write(qr_image_data)from django_otp.plugins.otp_totp.models import TOTPDevice
def verify_totp_token(user, token):
"""Verify TOTP token for user's device."""
# Get user's TOTP devices
devices = TOTPDevice.objects.filter(user=user, confirmed=True)
for device in devices:
if device.verify_token(token):
print(f"Token verified with device: {device.name}")
print(f"Device drift: {device.drift}")
return device
return None
# Usage
user = User.objects.get(username='alice')
device = verify_totp_token(user, '123456')from django_otp.plugins.otp_totp.models import TOTPDevice
def get_device_setup_info(device):
"""Get device setup information for user."""
info = {
'device_name': device.name,
'config_url': device.config_url,
'manual_setup': {
'secret_key': device.key,
'digits': device.digits,
'step': device.step,
'algorithm': 'SHA1'
},
'qr_code_data': device.config_url
}
return info
# Usage
device = TOTPDevice.objects.get(pk=1)
setup_info = get_device_setup_info(device)
# The config_url can be used in QR codes or manual setup
print(f"QR Code URL: {setup_info['config_url']}")
print(f"Manual key: {setup_info['manual_setup']['secret_key']}")from django_otp.plugins.otp_totp.models import TOTPDevice
from django_otp.oath import TOTP
def check_device_drift(device):
"""Check and display device drift information."""
totp = TOTP(device.bin_key, device.step, device.t0, device.digits)
current_t = totp.t()
info = {
'device_name': device.name,
'current_drift': device.drift,
'last_verified_t': device.last_t,
'current_t': current_t,
'time_difference_seconds': (current_t - device.last_t) * device.step if device.last_t >= 0 else None
}
return info
# Usage
device = TOTPDevice.objects.get(pk=1)
drift_info = check_device_drift(device)
print(f"Current drift: {drift_info['current_drift']} time steps")from django_otp.plugins.otp_totp.models import TOTPDevice
from django.contrib.auth.models import User
def setup_totp_for_users(usernames, device_name="Default TOTP"):
"""Set up TOTP devices for multiple users."""
devices = []
for username in usernames:
try:
user = User.objects.get(username=username)
device = TOTPDevice.objects.create(
user=user,
name=device_name,
confirmed=False
)
devices.append({
'username': username,
'device_id': device.pk,
'setup_url': device.config_url,
'key': device.key
})
except User.DoesNotExist:
print(f"User {username} not found")
return devices
# Usage
setup_info = setup_totp_for_users(['alice', 'bob', 'charlie'])
for info in setup_info:
print(f"User: {info['username']}, Key: {info['key']}")TOTP devices can be configured through Django settings:
# settings.py
# Enable drift synchronization (default: True)
OTP_TOTP_SYNC = True
# Throttling factor for failed verifications (default: 1)
OTP_TOTP_THROTTLE_FACTOR = 2
# Issuer name for QR codes (default: None)
OTP_TOTP_ISSUER = "My Company"
# Image URL for QR codes (default: None)
OTP_TOTP_IMAGE = "https://example.com/logo.png"TOTP relies on synchronized clocks between client and server. The tolerance field allows for some clock skew, and django-otp includes automatic drift compensation to handle gradual clock drift.
TOTP devices inherit from ThrottlingMixin, which provides exponential backoff for failed verification attempts. Configure the throttle factor through OTP_TOTP_THROTTLE_FACTOR.
Secret keys should be generated securely and stored safely. The keys are stored as hex-encoded strings in the database and should be backed up appropriately for disaster recovery.
Install with Tessl CLI
npx tessl i tessl/pypi-django-otp