CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-django-otp

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

Pending
Overview
Eval results
Files

django-integration.mddocs/

Django Integration

Forms, views, middleware, and decorators for integrating OTP authentication into Django applications. These components provide seamless integration with Django's built-in authentication system.

Capabilities

Forms

OTPAuthenticationForm

Complete authentication form with username, password, and OTP fields.

class OTPAuthenticationForm(OTPAuthenticationFormMixin, AuthenticationForm):
    """
    Complete OTP authentication form with username/password/token.
    
    Fields:
    - username: CharField - User identification
    - password: CharField - User password
    - otp_device: CharField - Device selection
    - otp_token: CharField - OTP token input
    - otp_challenge: CharField - Challenge generation button
    """
    
    otp_device = forms.CharField(widget=forms.Select)
    otp_token = forms.CharField(required=False, widget=forms.TextInput)
    otp_challenge = forms.CharField(required=False, widget=forms.TextInput)

OTPTokenForm

Token verification form for already authenticated users.

class OTPTokenForm(OTPAuthenticationFormMixin, forms.Form):
    """
    Token verification form for authenticated users.
    
    Fields:
    - otp_device: ChoiceField - Device selection
    - otp_token: CharField - OTP token input
    - otp_challenge: CharField - Challenge generation button
    """
    
    otp_device = forms.ChoiceField()
    otp_token = forms.CharField(required=False)
    otp_challenge = forms.CharField(required=False)
    
    def get_user(self):
        """Returns the authenticated user."""

OTPAuthenticationFormMixin

Shared functionality for OTP-aware authentication forms.

class OTPAuthenticationFormMixin:
    """Shared functionality for OTP-aware authentication forms."""
    
    otp_error_messages = {
        'invalid_token': 'Invalid token. Please make sure you have entered it correctly.',
        'token_required': 'Please enter your authentication code.',
        # ... more error messages
    }
    
    def clean_otp(self, user):
        """Process OTP fields and verify token."""
    
    @staticmethod
    def device_choices(user):
        """Return device choices for user."""

Views

LoginView

Two-factor authentication login view that handles both password and OTP verification.

class LoginView(auth_views.LoginView):
    """Two-factor authentication login view."""
    
    otp_authentication_form = OTPAuthenticationForm
    otp_token_form = OTPTokenForm
    
    @property
    def authentication_form(self):
        """Dynamically select form class based on authentication state."""
    
    def form_valid(self, form):
        """Handle successful form submission."""

login Function View

def login(request, **kwargs):
    """Function-based view wrapper for LoginView."""

Decorators

otp_required

Decorator that requires users to be verified by an OTP device.

def otp_required(view=None, redirect_field_name='next', login_url=None, if_configured=False):
    """
    Decorator requiring OTP verification.
    
    Parameters:
    - view: Function - View function (when used without parentheses)
    - redirect_field_name: str - Field name for redirect URL
    - login_url: str - URL to redirect to for authentication
    - if_configured: bool - Allow users with no devices if True
    
    Returns:
    Decorator function or decorated view
    """

Middleware

OTPMiddleware

Middleware that adds OTP device information to request.user.

class OTPMiddleware:
    """Middleware that adds OTP device information to request.user."""
    
    def __init__(self, get_response=None):
        """Initialize middleware."""
    
    def __call__(self, request):
        """Process request and add OTP info to user."""

is_verified Function

def is_verified(user) -> bool:
    """
    Check if user is verified by OTP device.
    
    Parameters:
    - user: User - User instance to check
    
    Returns:
    bool - True if user is OTP-verified
    """

Signals

otp_verification_failed = django.dispatch.Signal()

Signal sent when OTP verification fails, providing hooks for logging or additional security measures.

Usage Examples

Basic View Integration

from django_otp.decorators import otp_required
from django.shortcuts import render

@otp_required
def secure_view(request):
    """View requiring OTP verification."""
    return render(request, 'secure_page.html')

@otp_required(if_configured=True)
def optional_otp_view(request):
    """View requiring OTP only if user has devices configured."""
    return render(request, 'semi_secure_page.html')

Custom Login Flow

from django_otp.views import LoginView as OTPLoginView
from django.urls import reverse_lazy

class CustomOTPLoginView(OTPLoginView):
    """Custom login view with additional features."""
    
    template_name = 'custom_login.html'
    success_url = reverse_lazy('dashboard')
    
    def form_valid(self, form):
        """Add custom logic after successful login."""
        response = super().form_valid(form)
        
        # Log successful OTP login
        if hasattr(form, 'get_user') and hasattr(form.get_user(), 'otp_device'):
            device = form.get_user().otp_device
            print(f"User {form.get_user().username} logged in with {device.name}")
        
        return response

Middleware Usage

# settings.py
MIDDLEWARE = [
    # ... other middleware
    'django_otp.middleware.OTPMiddleware',
    # ... more middleware
]

# In views
from django_otp.middleware import is_verified

def my_view(request):
    if is_verified(request.user):
        # User is OTP-verified
        return render(request, 'secure_content.html')
    else:
        # User needs OTP verification
        return redirect('otp_login')

Custom OTP Forms

from django_otp.forms import OTPTokenForm
from django import forms

class CustomOTPForm(OTPTokenForm):
    """Custom OTP form with additional fields."""
    
    remember_device = forms.BooleanField(
        required=False,
        label="Remember this device for 30 days"
    )
    
    def __init__(self, user, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.user = user
        
        # Customize device choices
        device_choices = []
        for device in self.device_choices(user):
            device_choices.append((device[0], f"{device[1]} ({device[0][:8]}...)"))
        
        self.fields['otp_device'].choices = device_choices

Signal Handling

from django_otp.forms import otp_verification_failed
from django.dispatch import receiver
import logging

logger = logging.getLogger(__name__)

@receiver(otp_verification_failed)
def handle_otp_failure(sender, **kwargs):
    """Log OTP verification failures."""
    request = kwargs.get('request')
    user = kwargs.get('user')
    
    if request and user:
        logger.warning(
            f"OTP verification failed for user {user.username} "
            f"from IP {request.META.get('REMOTE_ADDR')}"
        )

URLconf Integration

# urls.py
from django_otp.views import LoginView
from django.urls import path

urlpatterns = [
    path('login/', LoginView.as_view(), name='login'),
    path('logout/', LogoutView.as_view(), name='logout'),
    # ... other URLs
]

Template Usage

<!-- login.html -->
<form method="post">
    {% csrf_token %}
    
    {{ form.username.label_tag }}
    {{ form.username }}
    
    {{ form.password.label_tag }}
    {{ form.password }}
    
    {% if form.otp_device %}
        {{ form.otp_device.label_tag }}
        {{ form.otp_device }}
        
        {{ form.otp_token.label_tag }}
        {{ form.otp_token }}
        
        {% if form.otp_challenge %}
            <button type="submit" name="otp_challenge" value="1">
                Send Challenge
            </button>
        {% endif %}
    {% endif %}
    
    <button type="submit">Login</button>
</form>

Configuration Settings

# settings.py

# URL for OTP login page (default: /login/)
OTP_LOGIN_URL = '/auth/login/'

# Hide sensitive data in admin (default: False)
OTP_ADMIN_HIDE_SENSITIVE_DATA = True

Install with Tessl CLI

npx tessl i tessl/pypi-django-otp

docs

admin-interface.md

core-authentication.md

device-models.md

django-integration.md

email-devices.md

hotp-devices.md

index.md

oath-algorithms.md

static-tokens.md

totp-devices.md

tile.json