Skip to content

Commit d7304af

Browse files
iMericaCopilot
andauthored
Add MFA/2FA support via standalone sub-package (#728)
* Adds Skeleton for MFA Support * Updates Docs + Linting Errors * Adds pytop test dep * Logs MFA events * Guards secret activation * Signs ephemeral token to couple to a current user * Solves race condition * Fixes state issue * Removes QR code dep * Migrates MFA docs to MKDocs * Update dj_rest_auth/mfa/serializers.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dj_rest_auth/mfa/serializers.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Adds more logging and post param hardening * Updates README * Updates test logic to match new behavior * Sorts imports properly * Tests MFA with use JWT is enabled * Prevents TOCTOU race condition * Updates post params * Updates post params * Include last_used_at * Overhauls/Modernizes Demo * Prevents timing attack + security related tweaks * Adds test coverage * Patches All Auth for CVE-2026-27982 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent c0c9c23 commit d7304af

75 files changed

Lines changed: 6075 additions & 549 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Secure drop-in authentication endpoints for Django REST Framework. Works seamles
1414

1515
- Login, logout, password change, password reset
1616
- User registration with email verification
17+
- Built-in MFA/2FA support (TOTP + recovery codes)
1718
- JWT authentication with HTTP-only cookies
1819
- Social auth (Google, GitHub, Facebook) via django-allauth
1920
- Fully customizable serializers
@@ -129,6 +130,21 @@ urlpatterns = [
129130
]
130131
```
131132

133+
## MFA / 2FA
134+
135+
```bash
136+
pip install 'dj-rest-auth[with-mfa]'
137+
```
138+
139+
MFA ships as an opt-in sub-package (`dj_rest_auth.mfa`) with:
140+
141+
- TOTP login challenge flow
142+
- Recovery codes
143+
- Security-focused defaults (short-lived MFA tokens, activation confirmation)
144+
145+
See the guide for setup and endpoint details:
146+
[MFA Guide](https://dj-rest-auth.readthedocs.io/en/latest/guides/mfa/)
147+
132148
## Documentation
133149

134150
Full documentation at **[dj-rest-auth.readthedocs.io](https://dj-rest-auth.readthedocs.io/)**
@@ -137,6 +153,7 @@ Full documentation at **[dj-rest-auth.readthedocs.io](https://dj-rest-auth.readt
137153
- [API Endpoints](https://dj-rest-auth.readthedocs.io/en/latest/api/endpoints/)
138154
- [JWT & Cookies Guide](https://dj-rest-auth.readthedocs.io/en/latest/guides/jwt-cookies/)
139155
- [Social Authentication](https://dj-rest-auth.readthedocs.io/en/latest/guides/social-auth/)
156+
- [MFA Guide](https://dj-rest-auth.readthedocs.io/en/latest/guides/mfa/)
140157

141158
## Contributing
142159

demo/.dockerignore

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Git
2+
.git
3+
.gitignore
4+
5+
# Python
6+
__pycache__/
7+
*.py[cod]
8+
*$py.class
9+
*.so
10+
.Python
11+
env/
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
backend/db.sqlite3
28+
29+
# Virtual Environment
30+
venv/
31+
.venv/
32+
.env
33+
34+
# Node
35+
node_modules/
36+
npm-debug.log
37+
yarn-error.log
38+
.next/
39+
40+
# IDEs
41+
.idea/
42+
.vscode/
43+
44+
# Project Specific
45+
spa-client/node_modules/
46+
spa-client/.next/

demo/README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# dj-rest-auth Demo Architecture
2+
3+
This directory contains a complete demonstration of a modern Single Page Application (SPA) authentication flow using `dj-rest-auth`.
4+
5+
## Overview
6+
7+
The demo consists of two main components:
8+
9+
1. **Backend (`demo/backend`)**: A Django project using `dj-rest-auth`, `django-allauth`, and `djangorestframework-simplejwt`. It exposes REST APIs for registration, login, and Multi-Factor Authentication (MFA).
10+
2. **Frontend (`demo/spa-client`)**: A Next.js application that consumes the backend APIs. It demonstrates a complete user flow including registration, login, MFA setup (QR code), and MFA verification.
11+
12+
## Architecture Diagram
13+
14+
```mermaid
15+
graph TD
16+
User[User / Browser]
17+
subgraph Frontend [Next.js App (Port 3000)]
18+
Pages[Pages]
19+
AuthContext[Auth Context]
20+
ApiClient[Axios Client]
21+
end
22+
subgraph Backend [Django API (Port 8000)]
23+
DjRestAuth[dj-rest-auth]
24+
AllAuth[django-allauth]
25+
MFA[MFA App]
26+
DB[(SQLite DB)]
27+
end
28+
29+
User -->|Interacts| Pages
30+
Pages -->|Uses| AuthContext
31+
AuthContext -->|Requests| ApiClient
32+
ApiClient -->|HTTP Requests| DjRestAuth
33+
34+
DjRestAuth -->|Auth Logic| AllAuth
35+
DjRestAuth -->|MFA Logic| MFA
36+
AllAuth -->|Persists| DB
37+
MFA -->|Persists| DB
38+
39+
note1[Login Flow]
40+
User -.->|1. Credentials| Pages
41+
Pages -.->|2. Login| DjRestAuth
42+
DjRestAuth -.->|3. MFA Required + Ephemeral Token| Pages
43+
Pages -.->|4. Verify Code + Token| DjRestAuth
44+
DjRestAuth -.->|5. Auth Token / Session| User
45+
```
46+
47+
## Running the Demo
48+
49+
The easiest way to run the demo is with Docker Compose:
50+
51+
```bash
52+
cd demo
53+
docker-compose up --build
54+
```
55+
- Frontend: [http://localhost:3000](http://localhost:3000)
56+
- Backend: [http://localhost:8000](http://localhost:8000)

demo/backend/Dockerfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM python:3.11-slim
2+
3+
WORKDIR /app
4+
5+
# Install system dependencies
6+
RUN apt-get update && apt-get install -y \
7+
build-essential \
8+
&& rm -rf /var/lib/apt/lists/*
9+
10+
# Copy local package and demo backend
11+
# Context is expected to be the project root
12+
COPY . /app/source
13+
14+
# Install the local package and backend requirements
15+
WORKDIR /app/source/demo/backend
16+
RUN pip install -r requirements.txt
17+
18+
# Run the server
19+
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
# SECURITY WARNING: don't run with debug turned on in production!
2323
DEBUG = True
2424

25-
ALLOWED_HOSTS = []
25+
ALLOWED_HOSTS = ['*']
2626

2727
# Application definition
2828

@@ -39,7 +39,9 @@
3939
'dj_rest_auth',
4040
'allauth',
4141
'allauth.account',
42+
'allauth.mfa',
4243
'dj_rest_auth.registration',
44+
'dj_rest_auth.mfa',
4345
'allauth.socialaccount',
4446
'allauth.socialaccount.providers.facebook',
4547
'drf_yasg',
@@ -111,10 +113,8 @@
111113
]
112114

113115
REST_AUTH = {
114-
'SESSION_LOGIN': True,
115-
'USE_JWT': True,
116-
'JWT_AUTH_COOKIE': 'auth',
117-
'JWT_AUTH_HTTPONLY': False,
116+
'SESSION_LOGIN': False,
117+
'USE_JWT': False,
118118
}
119119

120120
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
@@ -126,9 +126,7 @@
126126

127127
REST_FRAMEWORK = {
128128
'DEFAULT_AUTHENTICATION_CLASSES': (
129-
'rest_framework.authentication.SessionAuthentication',
130129
'rest_framework.authentication.TokenAuthentication',
131-
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
132130
),
133131
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
134132
}
@@ -139,7 +137,15 @@
139137
}
140138

141139

142-
# For demo purposes only. Use a white list in the real world.
143-
CORS_ORIGIN_ALLOW_ALL = True
140+
# CORS Configuration
141+
CORS_ALLOWED_ORIGINS = [
142+
"http://localhost:3000",
143+
"http://127.0.0.1:3000",
144+
]
145+
CORS_ALLOW_CREDENTIALS = True
146+
CSRF_TRUSTED_ORIGINS = [
147+
"http://localhost:3000",
148+
"http://127.0.0.1:3000",
149+
]
144150

145151
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from django.urls import include, re_path
22
from django.contrib import admin
33
from django.views.generic import RedirectView, TemplateView
4+
from dj_rest_auth.mfa.views import MFALoginView
45
from drf_yasg.views import get_schema_view
56
from drf_yasg import openapi
67

@@ -45,8 +46,10 @@
4546
TemplateView.as_view(template_name="password_reset_confirm.html"),
4647
name='password_reset_confirm'),
4748

49+
re_path(r'^dj-rest-auth/login/$', MFALoginView.as_view(), name='rest_login'),
4850
re_path(r'^dj-rest-auth/', include('dj_rest_auth.urls')),
4951
re_path(r'^dj-rest-auth/registration/', include('dj_rest_auth.registration.urls')),
52+
re_path(r'^dj-rest-auth/', include('dj_rest_auth.mfa.urls')),
5053
re_path(r'^account/', include('allauth.urls')),
5154
re_path(r'^admin/', admin.site.urls),
5255
re_path(r'^accounts/profile/$', RedirectView.as_view(url='/', permanent=True), name='profile-redirect'),
File renamed without changes.

demo/backend/requirements.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
django==5.2.10
2+
djangorestframework==3.16.1
3+
djangorestframework-simplejwt==5.5.1
4+
django-allauth[socialaccount,mfa]==65.14.3
5+
drf-yasg==1.21.7
6+
django-cors-headers==4.4.0
7+
coreapi==2.3.3
8+
setuptools==78.1.1
9+
qrcode==8.2
10+
pyotp==2.9.0
11+
../../

0 commit comments

Comments
 (0)