Python Multi-Factor Authentication Mfa Setup

Written by

in

In today’s threat‑rich digital landscape, relying on a single password is no longer enough to protect your applications. Python multi-factor authentication (MFA) setup provides an extra layer of security that combines something you know (a password) with something you have (a token, a mobile app, or a biometric). This guide walks you through the concepts, best practices, and step‑by‑step code snippets you need to implement robust MFA in your Python projects, whether you’re building a Flask API, a Django web app, or a simple command‑line tool.

Why MFA Matters for Python Applications

Multi‑factor authentication dramatically reduces the risk of credential‑stuffing attacks, phishing, and data breaches. By demanding a second verification factor, you make it exponentially harder for attackers to gain unauthorized access. For Python developers, integrating MFA also aligns with compliance standards such as GDPR, HIPAA, and PCI DSS, which increasingly require strong authentication mechanisms.

Core Concepts of Multi‑Factor Authentication

1. Types of Authentication Factors

  • Knowledge factor: Passwords, PINs, or security questions.
  • Possession factor: One‑time passwords (OTP) generated by authenticator apps, SMS codes, hardware tokens, or push notifications.
  • Inherence factor: Biometrics like fingerprint, facial recognition, or voice.

2. OTP Generation Methods

Two popular standards for generating OTPs are:

  • TOTP (Time‑Based OTP): Uses a shared secret and the current time to produce a 6‑digit code that expires after 30 seconds. Implemented via the pyotp library.
  • HOTP (HMAC‑Based OTP): Relies on a counter that increments each time an OTP is generated. Useful for hardware tokens.

3. Delivery Channels

Choosing the right delivery channel impacts usability and security:

  • Authenticator apps (Google Authenticator, Authy, Microsoft Authenticator)
  • SMS or voice calls (convenient but vulnerable to SIM swapping)
  • Email (acceptable for low‑risk scenarios)
  • Push notifications (real‑time approval via mobile app)

Preparing Your Python Environment

Before diving into code, ensure you have the following tools installed:

pip install pyotp flask django python-dotenv cryptography

These packages provide OTP generation (pyotp), web frameworks (flask, django), environment variable handling (python-dotenv), and encryption utilities (cryptography).

Step‑by‑Step MFA Setup in a Flask Application

Step 1: Configure Environment Variables

Store secret keys outside your codebase to prevent leaks. Create a .env file:

# .env
FLASK_SECRET_KEY=super_secret_key_123
MFA_ISSUER_NAME=MyPythonApp
MFA_ENCRYPTION_KEY=32byteslongbase64encodedkey==

Step 2: Initialize Flask and Load Secrets

from flask import Flask, request, session, redirect, url_for, render_template_string
from dotenv import load_dotenv
import os, pyotp, base64, hashlib, hmac

load_dotenv()
app = Flask(__name__)
app.secret_key = os.getenv('FLASK_SECRET_KEY')
ISSUER = os.getenv('MFA_ISSUER_NAME')
ENCRYPTION_KEY = base64.b64decode(os.getenv('MFA_ENCRYPTION_KEY'))

Step 3: Generate a User‑Specific TOTP Secret

When a user registers or enables MFA, create a unique secret and store it encrypted in your database.

def encrypt_secret(secret: str) -> str:
    # Simple HMAC‑based encryption for demonstration (use Fernet in production)
    digest = hmac.new(ENCRYPTION_KEY, secret.encode(), hashlib.sha256).digest()
    return base64.urlsafe_b64encode(digest).decode()

def generate_totp_secret() -> str:
    return pyotp.random_base32()

Step 4: Show the QR Code to the User

Most authenticator apps accept a QR code that encodes a otpauth:// URI.

import qrcode
import io

def get_qr_code_uri(username: str, secret: str) -> str:
    uri = pyotp.totp.TOTP(secret).provisioning_uri(name=username, issuer_name=ISSUER)
    return uri

def qr_image_bytes(uri: str) -> bytes:
    img = qrcode.make(uri)
    buf = io.BytesIO()
    img.save(buf, format='PNG')
    return buf.getvalue()

Step 5: Verify the OTP During Login

def verify_otp(secret: str, token: str) -> bool:
    totp = pyotp.TOTP(secret)
    return totp.verify(token, valid_window=1)  # Allows previous/next interval

Step 6: Assemble the Flask Routes

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form['username']
        # In a real app, save user record first
        secret = generate_totp_secret()
        encrypted = encrypt_secret(secret)
        # Save encrypted secret to DB (omitted)
        session['temp_secret'] = secret  # Store plain secret temporarily for QR generation
        return redirect(url_for('setup_mfa', username=username))
    return '''
        
Username:
''' @app.route('/setup-mfa') def setup_mfa(): username = request.args.get('username') secret = session.get('temp_secret') if not secret: return redirect(url_for('register')) uri = get_qr_code_uri(username, secret) img_bytes = qr_image_bytes(uri) img_base64 = base64.b64encode(img_bytes).decode() return f'''

Scan this QR code with your authenticator app

MFA QR

''' @app.route('/verify-mfa', methods=['POST']) def verify_mfa(): token = request.form['token'] secret = session.get('temp_secret') if verify_otp(secret, token): # Persist encrypted secret to user record here session.pop('temp_secret', None) return 'MFA enabled successfully!' return 'Invalid code, try again.', 401 @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] # Authenticate password (omitted) # Retrieve encrypted secret from DB, decrypt it secret = retrieve_decrypted_secret(username) # placeholder session['mfa_user'] = username session['mfa_secret'] = secret return redirect(url_for('login_mfa')) return '''
Username:
Password:
''' @app.route('/login-mfa', methods=['GET', 'POST']) def login_mfa(): if request.method == 'POST': token = request.form['token'] secret = session.get('mfa_secret') if verify_otp(secret, token): session.pop('mfa_secret', None) return f'Welcome, {session["mfa_user"]}!' return 'Invalid MFA code.', 401 return '''
MFA Code:
''' if __name__ == '__main__': app.run(debug=True)

Implementing MFA in Django: A Quick Overview

Django developers can leverage the same pyotp library, but Django’s built‑in authentication system simplifies user management.

  • Create a OneTimePassword model that stores the encrypted TOTP secret linked to settings.AUTH_USER_MODEL.
  • Use Django signals (post_save) to generate a secret when a user enables MFA.
  • Render the QR code in a template using the qrcode package and data:image/png;base64 URLs.
  • Add a custom authentication backend that first validates the password, then prompts for the OTP.

For a full‑featured solution, consider third‑party packages such as django-otp or django-two-factor-auth, which already handle backup tokens, trusted browsers, and recovery codes.

Best Practices for Secure MFA Implementation

  1. Never store plain‑text secrets. Encrypt TOTP secrets using a strong symmetric cipher (e.g., Fernet) and rotate keys periodically.
  2. Limit OTP verification attempts. Block the account after 5–10 failed attempts to mitigate brute‑force attacks.

Comments

Leave a Reply

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