Anasayfa / Cyber Security / Secure Your Django App: Implement Two-Factor Authentication Step‑by‑Step

Secure Your Django App: Implement Two-Factor Authentication Step‑by‑Step

Django security

In today’s threat‑rich landscape, a password alone is no longer enough to protect a Django web application. Two‑factor authentication (2FA) adds a second layer of verification—something the user has, like a mobile authenticator app—making it dramatically harder for attackers to gain unauthorized access. In this guide we’ll walk through the entire process of integrating 2FA into a Django project, from installing the right packages to hardening your deployment. By the end, you’ll have a production‑ready implementation that you can extend or customize to fit any workflow.

What You’ll Need

  • Python 3.8+ and a recent Django release (3.2 or newer)
  • Virtual environment (venv or virtualenv)
  • Basic Django project already created
  • Access to an email or SMS service for delivering OTP codes
  • pip packages: django‑otp, django‑two‑factor‑auth, qrcode, pillow

Step 1: Set Up the Project and Install Dependencies

Start by creating an isolated environment so your new packages don’t clash with global installs.

python -m venv env
source env/bin/activate  # on Windows use envScriptsactivate
pip install --upgrade pip
pip install django==4.2  # or your preferred version

Next, install the 2FA stack.

pip install django-otp django-two-factor-auth qrcode pillow

Make sure your requirements.txt is updated so the new libraries travel with your codebase.

Step 2: Choose a 2FA Library (django‑otp & django‑two‑factor‑auth)

While you could roll your own OTP generator, the community‑maintained django-two-factor-auth package builds on django-otp and provides ready‑made views, forms, and QR‑code integration. It supports time‑based one‑time passwords (TOTP) used by Google Authenticator, Authy, and most other apps.

Install it (already done in Step 1) and review the official documentation for any version‑specific notes.

Step 3: Configure Settings and Middleware

Open settings.py and add the required apps and middleware. The order matters: django_otp.middleware.OTPMiddleware must come after the session middleware.

INSTALLED_APPS += [
    'django_otp',
    'django_otp.plugins.otp_static',
    'django_otp.plugins.otp_totp',
    'two_factor',
]

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',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# Use the built‑in Two‑Factor backend first, then fall back to the default
AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',
]

# Optional: define where backup tokens are stored
OTP_TOTP_ISSUER = 'MyDjangoApp'

If you plan to send OTP codes via email, configure an email backend. For development you can use the console backend:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

In production replace it with SMTP or a third‑party service like SendGrid.

Step 4: Wire Up URLs and Templates

The package ships with a ready‑made URLconf. Include it in your project’s urls.py before your own authentication routes.

from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('account/', include('two_factor.urls', 'two_factor')),
    # Your existing app URLs go here
]

The default templates are decent, but you’ll probably want to customise the look and feel. Copy them from the package’s templates/two_factor directory into your project’s templates/two_factor folder and edit as needed. Remember to set TEMPLATE_DIRS or use DIRS inside TEMPLATES so Django picks up the overrides.

Step 5: Protect Sensitive Views with 2FA

Any view that handles critical data should require a verified OTP session. The library provides two helpers:

  • django_otp.decorators.otp_required – a function‑based decorator.
  • two_factor.views.LoginView – a class‑based view that already enforces OTP after the first successful password check.

Example for a function‑based view:

from django.contrib.auth.decorators import login_required
from django_otp.decorators import otp_required

@login_required
@otp_required
def dashboard(request):
    return render(request, 'dashboard.html')

If you use class‑based views, inherit from two_factor.views.AccessMixin or simply apply the decorator to the dispatch method.

Step 6: Test the Workflow and Deploy

Run the development server and create a test user:

python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

Navigate to /account/login/. After entering your credentials, you’ll be prompted to set up a TOTP device. Scan the QR code with Google Authenticator, Authy, or any compatible app. Enter the generated 6‑digit code to complete login.

Test edge cases:

  • What happens if the user enters a wrong code? – The view should display an error without locking the account.
  • Can the user log in with a backup token? – The package provides static backup tokens; verify they work.

Before pushing to production, enable HTTPS, set SESSION_COOKIE_SECURE = True and CSRF_COOKIE_SECURE = True, and switch the email backend to a real provider. Also, consider rate‑limiting OTP attempts to mitigate brute‑force attacks.

Common Mistakes to Avoid

1. Skipping middleware. Forgetting OTPMiddleware means the OTP verification flag never gets set, and users can bypass 2FA.

2. Using the default secret key in production. The OTP tokens are derived from the secret key; a compromised key defeats the whole system.

3. Not securing the QR code. If the QR image is cached publicly, an attacker could copy it and generate valid codes.

4. Relying on HTTP. OTP codes travel over the network; without TLS they can be intercepted.

5. Ignoring backup token management. Users who lose their device get locked out unless you enable static tokens or a recovery flow.

Tips and Tricks

• Enable SMS fallback. Combine django-otp with twilio to send OTPs via text for users without a smartphone.

• Custom email templates. Override two_factor/email/token_email.html to match your brand and include clear instructions.

• Adjust token lifetime. By default TOTP tokens are valid for 30 seconds. You can change OTP_TOTP_INTERVAL if you need a longer window for low‑end devices.

• Store backup tokens securely. They are plain strings; treat them like passwords and hash them if you implement your own storage.

• Log OTP events. Add a logger to django_otp signals so you can audit successful and failed verification attempts.

Frequently Asked Questions

Do I need to buy a third‑party service to send OTP codes?

No. The core TOTP flow works entirely offline using authenticator apps. If you want SMS or email delivery, you can use free tiers of services like Twilio or SendGrid, but they are optional.

Can I use authenticator apps other than Google Authenticator?

Absolutely. Any app that supports the standard TOTP algorithm (RFC 6238) will work—Authy, Microsoft Authenticator, and even open‑source options like FreeOTP are compatible.

What should I do if a user loses their device?

Provide a recovery flow that lets the user authenticate with a static backup token or via a verified email link. After verification, let them register a new TOTP device.

Conclusion

Adding two‑factor authentication to a Django web app dramatically raises the security bar with relatively little code. By leveraging django-otp and django-two-factor-auth, you get a battle‑tested solution that integrates cleanly with Django’s authentication system. Follow the steps above, avoid the common pitfalls, and you’ll give your users the confidence that their accounts are protected against credential‑stuffing attacks, phishing, and other modern threats. Happy coding, and stay secure!

Photo by Faisal on Unsplash

Etiketlendi: