Two-factor authentication (2FA) is becoming a standard requirement for modern applications, especially for APIs that use JWT authentication or separate frontend/backend architectures.
While working on Django REST Framework projects, I wanted a lightweight and API-focused way to add TOTP authentication without depending heavily on template-based flows or admin integrations.
So I built django-totp.
It is a reusable Django package that provides:
- TOTP enrollment
- QR generation
- backup recovery codes
- encrypted secret storage
- DRF endpoints
- helper utilities for multi-step authentication flows
PyPI: django-totp
Requirements
- Python 3.12+
- Django 5.0+
- Django REST Framework 3.15+
Installation
Install the package from PyPI:
pip install django-totp
Add the apps to your Django settings:
INSTALLED_APPS = [
# Django apps...
"rest_framework",
"django_totp",
]
Configure the Encryption Key
TOTP secrets and backup codes are stored using Fernet encryption.
Generate a key once
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Add it to your environment
TOTP_ENCRYPTION_KEY=your-generated-key
Load it in Django settings
import os
TOTP_ENCRYPTION_KEY = os.environ["TOTP_ENCRYPTION_KEY"]
Include the URLs
from django.urls import include, path
urlpatterns = [
path("api/", include("django_totp.urls")),
]
Run migrations:
python manage.py migrate
Available Endpoints
The package provides endpoints for the full enrollment lifecycle.
Create Enrollment
POST /api/totp/create/
Creates a TOTP secret and returns an SVG QR code.
Example response:
{
"svg": "<svg ...>...</svg>"
}
Confirm Enrollment
POST /api/totp/confirm/
Request:
{
"input_code": "123456"
}
Successful confirmation returns backup recovery codes.
Disable TOTP
POST /api/totp/disable/
Disables TOTP and removes backup codes.
Rotate Backup Codes
POST /api/totp/rotate_backup_codes/
Generates a new backup code set.
Example Login Flow
A common authentication flow looks like this:
1. Validate username/password
2. Check whether the user has TOTP enabled
3. Issue a temporary challenge token
4. Ask for TOTP or backup code
5. Verify the code
6. Issue final JWT/session
The package includes helper utilities for this flow.
Example:
from django_totp.auth import (
generate_challenge_token,
is_totp_enabled,
)
from django_totp.totp import verify_totp_code
Other Utilities
Useful helpers you can import directly:
- django_totp.auth
- is_totp_enabled(user)
- generate_challenge_token(user)
- verify_challenge_token(token)
- get_user_from_challenge_token(token)
- django_totp.totp
- generate_totp_secret()
- verify_totp_code(user, input_code)
- create_totp_setup(user)
- confirm_totp_setup(user, input_code)
- disable_totp(user)
- django_totp.backup_code_utils
- store_backup_codes(user, codes)
- verify_backup_code(user, input_code)
- rotate_backup_codes(user)
- django_totp.encryption
- generate_fernet_key()
- resolve_fernet_key(default=None)
- encrypt(value)
- decrypt(value)
Features
The package currently includes:
- Encrypted TOTP secret storage
- QR generation for authenticator apps
- Backup code generation and rotation
- One-time-use backup code validation
- DRF integration
- Configurable issuer name
- Endpoint throttling support
- Signed temporary challenge tokens
Why I Built It
Many existing Django 2FA solutions are designed primarily for server-rendered applications.
I wanted something focused more on:
- DRF APIs
- JWT authentication flows
- SPA/mobile frontends
- reusable API endpoints
The goal was to keep the package relatively lightweight while still covering common 2FA requirements.
Project Links
Feedback, issues, and contributions are welcome.
Top comments (0)