A pluggable framework for adding two-factor authentication to Django using one-time passwords.
—
Pre-generated backup codes for emergency access when primary OTP devices are unavailable. Static tokens are single-use codes that provide reliable backup authentication.
class StaticDevice(TimestampMixin, ThrottlingMixin, Device):
"""
Static token device for backup/emergency codes.
"""
def verify_token(self, token) -> bool:
"""
Verify and consume static token.
Parameters:
- token: str - Static token to verify
Returns:
bool - True if token was valid and consumed
"""
def get_throttle_factor(self) -> int:
"""Return throttle factor from settings."""class StaticToken(models.Model):
"""
Individual static token.
Fields:
- device: ForeignKey(StaticDevice) - Associated device
- token: CharField(max_length=16) - Token value
"""
device = models.ForeignKey(StaticDevice, on_delete=models.CASCADE)
token = models.CharField(max_length=16)
@staticmethod
def random_token() -> str:
"""Generate random token string."""class StaticDeviceAdmin(admin.ModelAdmin):
"""Admin interface for StaticDevice."""
class StaticTokenInline(admin.TabularInline):
"""Inline admin for StaticToken."""def add_static_token(username, token=None):
"""
Add static token to user (used by management command).
Parameters:
- username: str - Username to add token for
- token: str or None - Specific token value or None for random
Returns:
StaticToken - The created token instance
"""# Django management command
python manage.py addstatictoken <username> [token]from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
# Create device with tokens
device = StaticDevice.objects.create(
user=user,
name='Backup Codes',
confirmed=True
)
# Generate backup tokens
backup_codes = []
for i in range(10): # Generate 10 backup codes
token = StaticToken.objects.create(
device=device,
token=StaticToken.random_token()
)
backup_codes.append(token.token)
print("Your backup codes:")
for code in backup_codes:
print(f"- {code}")def verify_static_token(user, token):
"""Verify static token and show remaining count."""
try:
device = StaticDevice.objects.get(user=user, confirmed=True)
remaining_before = device.statictoken_set.count()
if device.verify_token(token):
remaining_after = device.statictoken_set.count()
return {
'valid': True,
'remaining_tokens': remaining_after,
'message': f"Token verified. {remaining_after} backup codes remaining."
}
else:
return {
'valid': False,
'remaining_tokens': remaining_before,
'message': "Invalid backup code."
}
except StaticDevice.DoesNotExist:
return {
'valid': False,
'remaining_tokens': 0,
'message': "No backup codes configured."
}
# Usage
result = verify_static_token(user, 'abc123def456')
print(result['message'])def regenerate_backup_codes(user, count=10):
"""Regenerate all backup codes for a user."""
try:
device = StaticDevice.objects.get(user=user)
# Remove existing tokens
device.statictoken_set.all().delete()
# Generate new tokens
new_codes = []
for i in range(count):
token = StaticToken.objects.create(
device=device,
token=StaticToken.random_token()
)
new_codes.append(token.token)
return new_codes
except StaticDevice.DoesNotExist:
# Create new device if none exists
device = StaticDevice.objects.create(
user=user,
name='Backup Codes',
confirmed=True
)
return regenerate_backup_codes(user, count)
# Usage
new_backup_codes = regenerate_backup_codes(user)# Add random token for user
python manage.py addstatictoken alice
# Add specific token for user
python manage.py addstatictoken alice mytoken123
# The command uses the add_static_token library functiondef get_backup_code_info(user):
"""Get backup code information for user."""
try:
device = StaticDevice.objects.get(user=user, confirmed=True)
tokens = device.statictoken_set.all()
return {
'has_backup_codes': True,
'total_codes': tokens.count(),
'codes': [token.token for token in tokens] # Only for admin display
}
except StaticDevice.DoesNotExist:
return {
'has_backup_codes': False,
'total_codes': 0,
'codes': []
}
# Usage (for admin/user dashboard)
info = get_backup_code_info(user)
print(f"User has {info['total_codes']} backup codes remaining")# settings.py
# Throttling factor for failed attempts (default: 1)
OTP_STATIC_THROTTLE_FACTOR = 2Static tokens are consumed immediately upon successful verification and cannot be reused. This prevents replay attacks but means users need multiple backup codes.
Backup codes should be stored securely by users (password manager, secure physical storage) and never transmitted over insecure channels.
Consider implementing a policy for periodic regeneration of backup codes, especially after any security incidents or when codes are running low.
Install with Tessl CLI
npx tessl i tessl/pypi-django-otp