A pluggable framework for adding two-factor authentication to Django using one-time passwords.
—
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.
Persist the given OTP device in the current session, linking it to the authenticated user.
def login(request, device):
"""
Persist the given OTP device in the current session.
Parameters:
- request: HttpRequest - The HTTP request object
- device: Device - The OTP device used to verify the user
Returns:
None
"""The device will be rejected if it does not belong to request.user. This is called automatically when django.contrib.auth.login is called with a user having an otp_device attribute.
Attempts to verify a token against a specific device, identified by its persistent ID.
def verify_token(user, device_id, token):
"""
Attempts to verify a token against a specific device.
Parameters:
- user: User - The user supplying the token
- device_id: str - A device's persistent_id value
- token: str - An OTP token to verify
Returns:
Device or None - The device that accepted the token, if any
"""This wraps the verification process in a database transaction to ensure that security policies like throttling are properly enforced.
Attempts to verify a token on every device attached to the given user until one succeeds.
def match_token(user, token):
"""
Attempts to verify a token on every device attached to the user.
WARNING: This function is deprecated and not recommended for use.
It may not interact well with recent features like throttling.
Parameters:
- user: User - The user supplying the token
- token: str - An OTP token to verify
Returns:
Device or None - The device that accepted the token, if any
"""Return an iterable of all devices registered to the given user.
def devices_for_user(user, confirmed=True, for_verify=False):
"""
Return an iterable of all devices registered to the given user.
Parameters:
- user: User - Standard or custom user object
- confirmed: bool or None - If None, all matching devices are returned.
Otherwise, limit to confirmed/unconfirmed devices
- for_verify: bool - If True, load devices with select_for_update to prevent
concurrent verifications. Must be called inside a transaction
Returns:
Generator yielding Device instances
"""Returns an empty iterable for anonymous users.
Return True if the user has at least one device.
def user_has_device(user, confirmed=True):
"""
Return True if the user has at least one device.
Parameters:
- user: User - Standard or custom user object
- confirmed: bool or None - If None, all matching devices are considered.
Otherwise, limit to confirmed/unconfirmed devices
Returns:
bool - True if user has at least one matching device
"""Returns False for anonymous users.
Returns an iterable of all loaded device models.
def device_classes():
"""
Returns an iterable of all loaded device models.
Returns:
Generator yielding Device model classes
"""Useful for discovering all available OTP device types in the current Django installation.
DEVICE_ID_SESSION_KEY = 'otp_device_id'Session key used to store the persistent ID of the user's current OTP device.
from django_otp import login, verify_token, devices_for_user
from django.contrib.auth import authenticate
from django.contrib.auth import login as auth_login
def two_factor_login(request, username, password, otp_token, device_id):
# First, authenticate with username/password
user = authenticate(request, username=username, password=password)
if not user:
return None, "Invalid credentials"
# Then verify OTP token
device = verify_token(user, device_id, otp_token)
if not device:
return None, "Invalid OTP token"
# Complete the login process
auth_login(request, user)
login(request, device) # This persists the OTP device
return user, "Login successful"from django_otp import devices_for_user, user_has_device
def get_user_devices(user):
if not user_has_device(user):
return []
devices = []
for device in devices_for_user(user):
devices.append({
'id': device.persistent_id,
'name': device.name,
'type': device.__class__.__name__,
'confirmed': device.confirmed
})
return devicesfrom django_otp import device_classes
def list_available_device_types():
types = []
for device_class in device_classes():
types.append({
'model': device_class.__name__,
'app_label': device_class._meta.app_label,
'verbose_name': device_class._meta.verbose_name
})
return typesInstall with Tessl CLI
npx tessl i tessl/pypi-django-otp