Skip to content

Commit f5755c1

Browse files
committed
Updates Docs + Linting Errors
1 parent 17c7240 commit f5755c1

10 files changed

Lines changed: 473 additions & 12 deletions

File tree

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,64 @@ REST_AUTH = {
4545
}
4646
```
4747

48+
## Multi-Factor Authentication (MFA)
49+
50+
dj-rest-auth supports TOTP-based Multi-Factor Authentication with recovery codes.
51+
52+
### Quick MFA Setup
53+
54+
1. Add `dj_rest_auth.mfa` to your `INSTALLED_APPS`:
55+
56+
```python
57+
INSTALLED_APPS = (
58+
...,
59+
'dj_rest_auth',
60+
'dj_rest_auth.mfa',
61+
)
62+
```
63+
64+
2. Run migrations:
65+
66+
```bash
67+
python manage.py migrate
68+
```
69+
70+
3. Include MFA URLs and use the MFA-enabled login view:
71+
72+
```python
73+
from dj_rest_auth.mfa.views import MFALoginView
74+
75+
urlpatterns = [
76+
path('dj-rest-auth/login/', MFALoginView.as_view(), name='rest_login'),
77+
path('dj-rest-auth/', include('dj_rest_auth.urls')),
78+
path('dj-rest-auth/', include('dj_rest_auth.mfa.urls')),
79+
]
80+
```
81+
82+
4. (Optional) Install `qrcode` for QR code generation:
83+
84+
```bash
85+
pip install qrcode[pil]
86+
```
87+
88+
### MFA Login Flow
89+
90+
```mermaid
91+
sequenceDiagram
92+
participant Client
93+
participant LoginEndpoint
94+
participant MFAVerify
95+
96+
Client->>LoginEndpoint: POST username + password
97+
LoginEndpoint-->>Client: ephemeral_token + mfa_required: true
98+
Client->>MFAVerify: POST ephemeral_token + TOTP code
99+
MFAVerify-->>Client: auth_token (JWT or session)
100+
```
101+
102+
When MFA is enabled for a user, the login endpoint returns an `ephemeral_token` instead of completing authentication. The client must then exchange this token along with a valid TOTP code (or recovery code) at the `/mfa/verify/` endpoint to receive the actual authentication token.
103+
104+
For full MFA documentation, see: https://dj-rest-auth.readthedocs.io/en/latest/mfa.html
105+
48106
### Testing
49107

50108
Install required modules with `pip install -r dj_rest_auth/tests/requirements.txt`

dj_rest_auth/mfa/migrations/0001_initial.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,22 @@ class Migration(migrations.Migration):
1717
migrations.CreateModel(
1818
name='Authenticator',
1919
fields=[
20-
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21-
('type', models.CharField(choices=[('totp', 'TOTP'), ('recovery_codes', 'Recovery codes')], max_length=20)),
20+
('id', models.BigAutoField(
21+
auto_created=True, primary_key=True,
22+
serialize=False, verbose_name='ID',
23+
)),
24+
('type', models.CharField(
25+
choices=[('totp', 'TOTP'), ('recovery_codes', 'Recovery codes')],
26+
max_length=20,
27+
)),
2228
('data', models.JSONField(default=dict)),
2329
('created_at', models.DateTimeField(auto_now_add=True)),
2430
('last_used_at', models.DateTimeField(blank=True, null=True)),
25-
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mfa_authenticators', to=settings.AUTH_USER_MODEL)),
31+
('user', models.ForeignKey(
32+
on_delete=django.db.models.deletion.CASCADE,
33+
related_name='mfa_authenticators',
34+
to=settings.AUTH_USER_MODEL,
35+
)),
2636
],
2737
options={
2838
'unique_together': {('user', 'type')},

dj_rest_auth/mfa/recovery_codes.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import os
44

55
from django.utils import timezone
6-
from django.utils.translation import gettext_lazy as _
76

87
from dj_rest_auth.app_settings import api_settings
98

dj_rest_auth/mfa/totp.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import pyotp
22
from django.utils import timezone
3-
from django.utils.translation import gettext_lazy as _
43

54
from dj_rest_auth.app_settings import api_settings
65

dj_rest_auth/mfa/urls.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,9 @@
1515
re_path(r'^mfa/totp/deactivate/?$', TOTPDeactivateView.as_view(), name='mfa_totp_deactivate'),
1616
re_path(r'^mfa/status/?$', MFAStatusView.as_view(), name='mfa_status'),
1717
re_path(r'^mfa/recovery-codes/?$', RecoveryCodesView.as_view(), name='mfa_recovery_codes'),
18-
re_path(r'^mfa/recovery-codes/regenerate/?$', RecoveryCodesRegenerateView.as_view(), name='mfa_recovery_codes_regenerate'),
18+
re_path(
19+
r'^mfa/recovery-codes/regenerate/?$',
20+
RecoveryCodesRegenerateView.as_view(),
21+
name='mfa_recovery_codes_regenerate',
22+
),
1923
]

dj_rest_auth/mfa/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from django.contrib.auth import get_user_model
2-
from django.core.signing import BadSignature, SignatureExpired, TimestampSigner
2+
from django.core.signing import TimestampSigner
33

44
from dj_rest_auth.app_settings import api_settings
55

dj_rest_auth/tests/test_mfa.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from django.contrib.auth import get_user_model
33
from django.test import TestCase, override_settings, modify_settings
44

5-
from dj_rest_auth.mfa.models import Authenticator
65
from dj_rest_auth.mfa.totp import TOTP, generate_totp_secret, validate_totp_code
76
from dj_rest_auth.mfa.recovery_codes import RecoveryCodes
87
from dj_rest_auth.mfa.utils import create_ephemeral_token, verify_ephemeral_token, is_mfa_enabled
@@ -223,7 +222,7 @@ def test_mfa_verify_invalid_code(self):
223222

224223
def test_mfa_verify_invalid_token(self):
225224
"""Verify MFA with invalid ephemeral token should fail."""
226-
response = self.post(
225+
self.post(
227226
self.mfa_verify_url,
228227
data={'ephemeral_token': 'invalid-token', 'code': '123456'},
229228
status_code=400,
@@ -277,7 +276,7 @@ def test_totp_deactivate(self):
277276

278277
totp = pyotp.TOTP(secret)
279278
code = totp.now()
280-
response = self.post(
279+
self.post(
281280
self.totp_deactivate_url,
282281
data={'code': code},
283282
status_code=200,
@@ -290,7 +289,7 @@ def test_totp_deactivate_invalid_code(self):
290289
TOTP.activate(self.user, secret)
291290
self._mfa_login_get_token(secret)
292291

293-
response = self.post(
292+
self.post(
294293
self.totp_deactivate_url,
295294
data={'code': '000000'},
296295
status_code=400,
@@ -321,7 +320,7 @@ def test_recovery_codes_regenerate(self):
321320
def test_recovery_codes_regenerate_without_mfa(self):
322321
"""Should fail to regenerate when MFA is not enabled."""
323322
self._login_get_token()
324-
response = self.post(self.recovery_codes_regenerate_url, status_code=400)
323+
self.post(self.recovery_codes_regenerate_url, status_code=400)
325324

326325
def test_full_mfa_flow(self):
327326
"""End-to-end: activate MFA, login with TOTP, login with recovery code,

docs/configuration.rst

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,3 +251,90 @@ views when using the JWT cookie for auth. It does not effect a client's ability
251251
to authenticate using a JWT Bearer Auth header without a CSRF token (though
252252
getting the JWT token in the first place without passing a CSRF token isnt
253253
possible). Default is ``False``.
254+
255+
256+
MFA Settings
257+
============
258+
259+
The following settings configure Multi-Factor Authentication behavior. These are
260+
only relevant when ``dj_rest_auth.mfa`` is included in ``INSTALLED_APPS``.
261+
262+
.. note:: For complete MFA documentation, see :doc:`MFA (Multi-Factor Auth) </mfa>`.
263+
264+
``MFA_EPHEMERAL_TOKEN_TIMEOUT``
265+
===============================
266+
267+
Time in seconds before the ephemeral token (returned during MFA login flow) expires.
268+
After this timeout, the user must re-authenticate with username/password.
269+
Default is ``300`` (5 minutes).
270+
271+
``MFA_TOTP_DIGITS``
272+
===================
273+
274+
Number of digits in the TOTP code. Most authenticator apps use 6 digits.
275+
Default is ``6``.
276+
277+
``MFA_TOTP_PERIOD``
278+
===================
279+
280+
Time period in seconds for TOTP code validity. This determines how often a new
281+
code is generated. Standard value is 30 seconds. Default is ``30``.
282+
283+
``MFA_TOTP_ISSUER``
284+
===================
285+
286+
Issuer name displayed in authenticator apps. This helps users identify which
287+
account the TOTP is for. Default is ``''`` (empty string).
288+
289+
Example:
290+
291+
.. code-block:: python
292+
293+
REST_AUTH = {
294+
'MFA_TOTP_ISSUER': 'MyApp',
295+
}
296+
297+
``MFA_RECOVERY_CODE_COUNT``
298+
===========================
299+
300+
Number of recovery codes to generate when MFA is activated. Recovery codes
301+
provide backup access if the authenticator device is lost. Default is ``10``.
302+
303+
``MFA_VERIFY_SERIALIZER``
304+
=========================
305+
306+
The path to the serializer class for MFA verification (exchanging ephemeral token
307+
+ TOTP code for auth token). Default is
308+
``'dj_rest_auth.mfa.serializers.MFAVerifySerializer'``.
309+
310+
``MFA_TOTP_ACTIVATE_INIT_SERIALIZER``
311+
=====================================
312+
313+
The path to the serializer class for the TOTP activation initialization response
314+
(GET request to generate secret). Default is
315+
``'dj_rest_auth.mfa.serializers.TOTPActivateInitSerializer'``.
316+
317+
``MFA_TOTP_ACTIVATE_CONFIRM_SERIALIZER``
318+
========================================
319+
320+
The path to the serializer class for confirming TOTP activation (POST request
321+
with secret and code). Default is
322+
``'dj_rest_auth.mfa.serializers.TOTPActivateConfirmSerializer'``.
323+
324+
``MFA_TOTP_DEACTIVATE_SERIALIZER``
325+
==================================
326+
327+
The path to the serializer class for TOTP deactivation. Default is
328+
``'dj_rest_auth.mfa.serializers.TOTPDeactivateSerializer'``.
329+
330+
``MFA_STATUS_SERIALIZER``
331+
=========================
332+
333+
The path to the serializer class for the MFA status response. Default is
334+
``'dj_rest_auth.mfa.serializers.MFAStatusSerializer'``.
335+
336+
``MFA_RECOVERY_CODES_SERIALIZER``
337+
=================================
338+
339+
The path to the serializer class for recovery codes responses. Default is
340+
``'dj_rest_auth.mfa.serializers.RecoveryCodesSerializer'``.

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Contents
2020
Installation <installation>
2121
API endpoints <api_endpoints>
2222
Configuration <configuration>
23+
MFA (Multi-Factor Auth) <mfa>
2324
Demo project <demo>
2425
FAQ <faq>
2526
Disclosure Policy <disclosure>

0 commit comments

Comments
 (0)