Django Two-Factor Authentication
Complete Two-Factor Authentication for Django. Built on top of the django-otp framework and Django's built-in authentication system, it offers easy integration into most Django projects. Inspired by Google's Two-Step Authentication, it supports various methods including token generator apps (like Google Authenticator), SMS, call, and YubiKey. The library is actively maintained with frequent releases, typically every few months.
Warnings
- breaking In version 1.14.0, the phone capability was moved to a separate plugin. If you used phone-based authentication, you must add 'two_factor.plugins.phonenumber' to your INSTALLED_APPS to restore this functionality.
- gotcha The `django_otp.middleware.OTPMiddleware` must be placed in `MIDDLEWARE` *after* `django.contrib.auth.middleware.AuthenticationMiddleware`. Incorrect ordering can lead to authentication issues.
- gotcha If using optional authentication methods (e.g., SMS, email, YubiKey, WebAuthn), ensure their respective plugins (e.g., `django_otp.plugins.otp_email`, `two_factor.plugins.phonenumber`) are correctly listed in `INSTALLED_APPS` and any necessary extra dependencies are installed.
- breaking In version 1.16.0, the minimal `webauthn` dependency was upgraded to 2.0, which removed its `pydantic` dependency. If you were relying on `pydantic` through `webauthn`, you might need to add it as a direct dependency.
- gotcha To prevent circumvention of two-factor authentication, ensure that any other login routes in your project (e.g., `django.contrib.auth.urls`) are removed or disabled once `django-two-factor-auth` is configured as the primary login.
- gotcha Prior to 1.18.0, the documentation for setting up YubiKey support or email gateways occasionally missed informing users to add the corresponding `django-otp` or `two_factor` plugin to `INSTALLED_APPS`. This was fixed in docs, but the underlying common mistake remains.
- gotcha An infinite redirect issue with `AdminSiteOTPRequiredMixin` on the admin site was fixed in 1.18.0. If you are using this mixin, ensure you are on a compatible version to avoid login loops.
Install
-
pip install django-two-factor-auth -
pip install django-two-factor-auth[phonenumbers] -
pip install django-two-factor-auth[webauthn]
Imports
- urlpatterns
from two_factor.urls import urlpatterns as tf_urls
- OTPMiddleware
from django_otp.middleware import OTPMiddleware
- AdminSiteOTPRequiredMixin
from two_factor.admin import AdminSiteOTPRequiredMixin
Quickstart
import os
# settings.py
# Add required apps, ensuring 'two_factor' is listed after django_otp plugins
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_otp',
'django_otp.plugins.otp_static',
'django_otp.plugins.otp_totp',
# Optional plugins:
# 'django_otp.plugins.otp_email', # For email tokens
# 'two_factor.plugins.phonenumber', # For SMS/call tokens
# 'two_factor.plugins.email', # For email tokens (alternative)
# 'two_factor.plugins.yubikey', # For YubiKey support
# 'webauthn', # For WebAuthn support
'two_factor',
]
# Add OTP middleware after AuthenticationMiddleware
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_otp.middleware.OTPMiddleware', # Must be after AuthenticationMiddleware
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# Point to the two-factor authentication login/profile URLs
LOGIN_URL = 'two_factor:login'
LOGIN_REDIRECT_URL = 'two_factor:profile'
# Optional: Configure email backend if using email tokens
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'webmaster@localhost')
# urls.py
from django.contrib import admin
from django.urls import path, include
from two_factor.urls import urlpatterns as tf_urls
urlpatterns = [
path('admin/', admin.site.urls),
path('', include(tf_urls)), # Include two-factor URLs at the root or desired path
# path('account/', include(tf_urls)), # Alternative: include at a specific path
]
# To integrate with Django Admin (optional, and usually patched automatically)
# from two_factor.admin import AdminSiteOTPRequiredMixin
# class OTPAdminSite(AdminSiteOTPRequiredMixin, admin.AdminSite):
# pass
# admin.site = OTPAdminSite() # Replace default admin site if needed, often not required.