Python Multi-Factor Authentication Mfa Setup

Written by

in

In today’s security‑first landscape, relying on a single password is no longer enough to protect your Python applications. Multi‑factor authentication (MFA) adds an extra layer of defense by requiring users to prove their identity with something they know, something they have, or even something they are. This guide walks you through the fundamentals of MFA, why it matters for Python developers, and provides step‑by‑step code examples for integrating TOTP‑based MFA into popular frameworks like Flask and Django.

What Is Multi‑Factor Authentication and Why It Matters

Multi‑factor authentication combines two or more independent credentials to verify a user’s identity. The most common factors are:

  • Knowledge: a password or PIN.
  • Possession: a smartphone app (Google Authenticator, Authy), a hardware token, or an SMS code.
  • Inherence: biometric data such as fingerprints or facial recognition.

Implementing MFA dramatically reduces the risk of credential stuffing, phishing, and brute‑force attacks. According to a Verizon Data Breach Investigation Report, breaches involving compromised passwords drop by up to 99% when MFA is enforced.

Choosing the Right MFA Method for Python Projects

Python developers have several options when it comes to MFA. The most widely adopted approach for web applications is Time‑Based One‑Time Passwords (TOTP), which are generated by standard authenticator apps and follow the RFC 6238 specification. TOTP is:

  • Open‑source and free.
  • Supported across Android, iOS, and desktop platforms.
  • Easy to implement with libraries like pyotp and django-otp.

If you need a higher assurance level, consider integrating FIDO2/WebAuthn for hardware keys, or using third‑party services such as Auth0, Okta, or Duo Security. This article focuses on TOTP because it strikes a balance between security, user experience, and implementation effort.

Setting Up a Basic TOTP MFA Flow in Python

Prerequisites

  1. Python 3.8+ installed.
  2. A virtual environment (recommended).
  3. Basic familiarity with Flask or Django.
  4. Install required packages:
    pip install pyotp qrcode[pil] pillow

Generating a Secret and QR Code

The secret key is a base‑32 string stored securely for each user. It can be displayed as a QR code that authenticator apps scan.

import pyotp
import qrcode
from io import BytesIO
import base64

def generate_mfa_secret(username: str) -> str:
    # Create a unique secret per user
    secret = pyotp.random_base32()
    # Store `secret` in your user database (hashed or encrypted)
    return secret

def get_qr_code_uri(secret: str, username: str, issuer: str = "MyApp"):
    # Build the provisioning URI (compatible with Google Authenticator)
    totp = pyotp.TOTP(secret)
    return totp.provisioning_uri(name=username, issuer_name=issuer)

def qr_code_image(uri: str) -> str:
    # Generate PNG image and encode as base64 for embedding in HTML
    img = qrcode.make(uri)
    buffer = BytesIO()
    img.save(buffer, format="PNG")
    return "data:image/png;base64," + base64.b64encode(buffer.getvalue()).decode()

Verifying a User‑Submitted Token

When a user logs in and provides the 6‑digit code from their authenticator app, you validate it against the stored secret.

def verify_mfa_token(secret: str, token: str) -> bool:
    totp = pyotp.TOTP(secret)
    # `verify` accepts a window of ±1 step (30 seconds) to tolerate clock drift
    return totp.verify(token, valid_window=1)

Integrating MFA into a Flask Application

Project Structure

.
├─ app.py
├─ models.py   # simple user model with `mfa_secret`
├─ templates/
│   ├─ login.html
│   ├─ mfa_setup.html
│   └─ mfa_verify.html
└─ requirements.txt

Key Flask Routes

from flask import Flask, render_template, request, redirect, session, url_for, flash
from models import User, db_session
import pyotp, qrcode, base64, io

app = Flask(__name__)
app.secret_key = "super‑secret‑key"

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        user = User.query.filter_by(username=request.form["username"]).first()
        if user and user.check_password(request.form["password"]):
            session["user_id"] = user.id
            if user.mfa_secret:
                return redirect(url_for("mfa_verify"))
            else:
                return redirect(url_for("mfa_setup"))
        flash("Invalid credentials")
    return render_template("login.html")

@app.route("/mfa/setup", methods=["GET", "POST"])
def mfa_setup():
    user = User.query.get(session["user_id"])
    if request.method == "POST":
        token = request.form["token"]
        if verify_mfa_token(user.mfa_secret, token):
            flash("MFA enabled successfully")
            return redirect(url_for("dashboard"))
        flash("Invalid token")
    else:
        # First time: generate secret and QR code
        if not user.mfa_secret:
            user.mfa_secret = generate_mfa_secret(user.username)
            db_session.commit()
        uri = get_qr_code_uri(user.mfa_secret, user.username)
        qr_data = qr_code_image(uri)
        return render_template("mfa_setup.html", qr_data=qr_data)

@app.route("/mfa/verify", methods=["GET", "POST"])
def mfa_verify():
    user = User.query.get(session["user_id"])
    if request.method == "POST":
        token = request.form["token"]
        if verify_mfa_token(user.mfa_secret, token):
            session["mfa_verified"] = True
            return redirect(url_for("dashboard"))
        flash("Invalid token")
    return render_template("mfa_verify.html")

Template Snippet for QR Code Display

<!-- mfa_setup.html -->
<h2>Set Up Multi‑Factor Authentication</h2>
<p>Scan the QR code below with Google Authenticator, Authy, or any TOTP app.</p>
<img src="{{ qr_data }}" alt="MFA QR Code" />
<form method="post">
    <label for="token">Enter the 6‑digit code:</label>
    <input type="text" name="token" id="token" required />
    <button type="submit">Verify</button>
</form>

Adding MFA to a Django Project

Leverage django-otp and django-two-factor-auth

Both packages provide ready‑made views, models, and middleware. Install them with:

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

Configuration Steps

  1. Add the apps to INSTALLED_APPS:
    INSTALLED_APPS = [
        ...,
        'django_otp',
        'django_otp.plugins.otp_totp',
        'two_factor',
        'two_factor.plugins.phonenumber',
    ]
  2. Insert the OTP middleware before authentication middleware:
    MIDDLEWARE = [
        'django_otp.middleware.OTPMiddleware',
        ...,
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        ...,
    ]
  3. Update urls.py to include the two‑factor URLs:
    from django.urls import path, include
    urlpatterns = [
        path('', include('two_factor.urls', 'two_factor')),
        # other app URLs
    ]
  4. Run migrations:
    python manage.py migrate

Customizing the Login Flow

If you already have a bespoke login view, you can wrap it with LoginView from two_factor.views:

from two_factor.views import LoginView

class MyLoginView(LoginView):
    template_name = 'myapp/login.html'
    # You can override form_class, success_url, etc.

Best Practices for Secure MFA Implementation

  • Store secrets encrypted. Never keep the TOTP secret in plain text; use a server‑side encryption key or a vault service.
  • Enforce backup codes. Provide users with a set of one‑time use backup codes for situations where their authenticator device is unavailable.
  • Rate‑limit verification attempts. Limit failed MFA attempts to mitigate brute‑force attacks.
  • Use HTTPS everywhere. MFA tokens are sensitive; TLS protects them from eavesdropping.
  • Educate users. Explain why MFA is required and give clear instructions for setup.
  • Log MFA events. Record successful and failed MFA challenges for audit trails.

Testing Your MFA Setup

Automated tests should cover both happy‑path and edge cases. Below is a pytest example

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *