0
# Django Integration
1
2
Forms, views, middleware, and decorators for integrating OTP authentication into Django applications. These components provide seamless integration with Django's built-in authentication system.
3
4
## Capabilities
5
6
### Forms
7
8
#### OTPAuthenticationForm
9
10
Complete authentication form with username, password, and OTP fields.
11
12
```python { .api }
13
class OTPAuthenticationForm(OTPAuthenticationFormMixin, AuthenticationForm):
14
"""
15
Complete OTP authentication form with username/password/token.
16
17
Fields:
18
- username: CharField - User identification
19
- password: CharField - User password
20
- otp_device: CharField - Device selection
21
- otp_token: CharField - OTP token input
22
- otp_challenge: CharField - Challenge generation button
23
"""
24
25
otp_device = forms.CharField(widget=forms.Select)
26
otp_token = forms.CharField(required=False, widget=forms.TextInput)
27
otp_challenge = forms.CharField(required=False, widget=forms.TextInput)
28
```
29
30
#### OTPTokenForm
31
32
Token verification form for already authenticated users.
33
34
```python { .api }
35
class OTPTokenForm(OTPAuthenticationFormMixin, forms.Form):
36
"""
37
Token verification form for authenticated users.
38
39
Fields:
40
- otp_device: ChoiceField - Device selection
41
- otp_token: CharField - OTP token input
42
- otp_challenge: CharField - Challenge generation button
43
"""
44
45
otp_device = forms.ChoiceField()
46
otp_token = forms.CharField(required=False)
47
otp_challenge = forms.CharField(required=False)
48
49
def get_user(self):
50
"""Returns the authenticated user."""
51
```
52
53
#### OTPAuthenticationFormMixin
54
55
Shared functionality for OTP-aware authentication forms.
56
57
```python { .api }
58
class OTPAuthenticationFormMixin:
59
"""Shared functionality for OTP-aware authentication forms."""
60
61
otp_error_messages = {
62
'invalid_token': 'Invalid token. Please make sure you have entered it correctly.',
63
'token_required': 'Please enter your authentication code.',
64
# ... more error messages
65
}
66
67
def clean_otp(self, user):
68
"""Process OTP fields and verify token."""
69
70
@staticmethod
71
def device_choices(user):
72
"""Return device choices for user."""
73
```
74
75
### Views
76
77
#### LoginView
78
79
Two-factor authentication login view that handles both password and OTP verification.
80
81
```python { .api }
82
class LoginView(auth_views.LoginView):
83
"""Two-factor authentication login view."""
84
85
otp_authentication_form = OTPAuthenticationForm
86
otp_token_form = OTPTokenForm
87
88
@property
89
def authentication_form(self):
90
"""Dynamically select form class based on authentication state."""
91
92
def form_valid(self, form):
93
"""Handle successful form submission."""
94
```
95
96
#### login Function View
97
98
```python { .api }
99
def login(request, **kwargs):
100
"""Function-based view wrapper for LoginView."""
101
```
102
103
### Decorators
104
105
#### otp_required
106
107
Decorator that requires users to be verified by an OTP device.
108
109
```python { .api }
110
def otp_required(view=None, redirect_field_name='next', login_url=None, if_configured=False):
111
"""
112
Decorator requiring OTP verification.
113
114
Parameters:
115
- view: Function - View function (when used without parentheses)
116
- redirect_field_name: str - Field name for redirect URL
117
- login_url: str - URL to redirect to for authentication
118
- if_configured: bool - Allow users with no devices if True
119
120
Returns:
121
Decorator function or decorated view
122
"""
123
```
124
125
### Middleware
126
127
#### OTPMiddleware
128
129
Middleware that adds OTP device information to request.user.
130
131
```python { .api }
132
class OTPMiddleware:
133
"""Middleware that adds OTP device information to request.user."""
134
135
def __init__(self, get_response=None):
136
"""Initialize middleware."""
137
138
def __call__(self, request):
139
"""Process request and add OTP info to user."""
140
```
141
142
#### is_verified Function
143
144
```python { .api }
145
def is_verified(user) -> bool:
146
"""
147
Check if user is verified by OTP device.
148
149
Parameters:
150
- user: User - User instance to check
151
152
Returns:
153
bool - True if user is OTP-verified
154
"""
155
```
156
157
### Signals
158
159
```python { .api }
160
otp_verification_failed = django.dispatch.Signal()
161
```
162
163
Signal sent when OTP verification fails, providing hooks for logging or additional security measures.
164
165
## Usage Examples
166
167
### Basic View Integration
168
169
```python
170
from django_otp.decorators import otp_required
171
from django.shortcuts import render
172
173
@otp_required
174
def secure_view(request):
175
"""View requiring OTP verification."""
176
return render(request, 'secure_page.html')
177
178
@otp_required(if_configured=True)
179
def optional_otp_view(request):
180
"""View requiring OTP only if user has devices configured."""
181
return render(request, 'semi_secure_page.html')
182
```
183
184
### Custom Login Flow
185
186
```python
187
from django_otp.views import LoginView as OTPLoginView
188
from django.urls import reverse_lazy
189
190
class CustomOTPLoginView(OTPLoginView):
191
"""Custom login view with additional features."""
192
193
template_name = 'custom_login.html'
194
success_url = reverse_lazy('dashboard')
195
196
def form_valid(self, form):
197
"""Add custom logic after successful login."""
198
response = super().form_valid(form)
199
200
# Log successful OTP login
201
if hasattr(form, 'get_user') and hasattr(form.get_user(), 'otp_device'):
202
device = form.get_user().otp_device
203
print(f"User {form.get_user().username} logged in with {device.name}")
204
205
return response
206
```
207
208
### Middleware Usage
209
210
```python
211
# settings.py
212
MIDDLEWARE = [
213
# ... other middleware
214
'django_otp.middleware.OTPMiddleware',
215
# ... more middleware
216
]
217
218
# In views
219
from django_otp.middleware import is_verified
220
221
def my_view(request):
222
if is_verified(request.user):
223
# User is OTP-verified
224
return render(request, 'secure_content.html')
225
else:
226
# User needs OTP verification
227
return redirect('otp_login')
228
```
229
230
### Custom OTP Forms
231
232
```python
233
from django_otp.forms import OTPTokenForm
234
from django import forms
235
236
class CustomOTPForm(OTPTokenForm):
237
"""Custom OTP form with additional fields."""
238
239
remember_device = forms.BooleanField(
240
required=False,
241
label="Remember this device for 30 days"
242
)
243
244
def __init__(self, user, *args, **kwargs):
245
super().__init__(*args, **kwargs)
246
self.user = user
247
248
# Customize device choices
249
device_choices = []
250
for device in self.device_choices(user):
251
device_choices.append((device[0], f"{device[1]} ({device[0][:8]}...)"))
252
253
self.fields['otp_device'].choices = device_choices
254
```
255
256
### Signal Handling
257
258
```python
259
from django_otp.forms import otp_verification_failed
260
from django.dispatch import receiver
261
import logging
262
263
logger = logging.getLogger(__name__)
264
265
@receiver(otp_verification_failed)
266
def handle_otp_failure(sender, **kwargs):
267
"""Log OTP verification failures."""
268
request = kwargs.get('request')
269
user = kwargs.get('user')
270
271
if request and user:
272
logger.warning(
273
f"OTP verification failed for user {user.username} "
274
f"from IP {request.META.get('REMOTE_ADDR')}"
275
)
276
```
277
278
### URLconf Integration
279
280
```python
281
# urls.py
282
from django_otp.views import LoginView
283
from django.urls import path
284
285
urlpatterns = [
286
path('login/', LoginView.as_view(), name='login'),
287
path('logout/', LogoutView.as_view(), name='logout'),
288
# ... other URLs
289
]
290
```
291
292
### Template Usage
293
294
```html
295
<!-- login.html -->
296
<form method="post">
297
{% csrf_token %}
298
299
{{ form.username.label_tag }}
300
{{ form.username }}
301
302
{{ form.password.label_tag }}
303
{{ form.password }}
304
305
{% if form.otp_device %}
306
{{ form.otp_device.label_tag }}
307
{{ form.otp_device }}
308
309
{{ form.otp_token.label_tag }}
310
{{ form.otp_token }}
311
312
{% if form.otp_challenge %}
313
<button type="submit" name="otp_challenge" value="1">
314
Send Challenge
315
</button>
316
{% endif %}
317
{% endif %}
318
319
<button type="submit">Login</button>
320
</form>
321
```
322
323
## Configuration Settings
324
325
```python
326
# settings.py
327
328
# URL for OTP login page (default: /login/)
329
OTP_LOGIN_URL = '/auth/login/'
330
331
# Hide sensitive data in admin (default: False)
332
OTP_ADMIN_HIDE_SENSITIVE_DATA = True
333
```