Python Multi-Factor Authentication Mfa Setup

Written by

in

In today’s security‑first landscape, a single password is no longer enough to protect user accounts. 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 something they are. If you’re a Python developer looking to bolster the security of your web or API projects, this guide walks you through everything you need to know to set up robust MFA—from the theory behind it to hands‑on code examples using popular libraries and frameworks.

Why MFA Matters for Python Applications

Cyber‑criminals constantly evolve their tactics, and credential stuffing attacks have surged by more than 50 % in the past two years. Implementing MFA can:

  • Reduce the risk of unauthorized access by requiring a second verification step.
  • Boost user trust—customers feel safer knowing their data is protected.
  • Help meet compliance standards such as GDPR, HIPAA, and PCI‑DSS, which often mandate MFA for privileged accounts.
  • Improve overall security posture without drastically changing your existing authentication flow.

Core Concepts Behind Multi‑Factor Authentication

Types of Factors

MFA combines at least two of the following:

  • Knowledge factor – something the user knows (password, PIN).
  • Possession factor – something the user has (smartphone, hardware token).
  • Inherence factor – something the user is (fingerprint, facial recognition).

Common MFA Methods in Python

When building a Python solution, the most widely adopted methods are:

  1. Time‑Based One‑Time Passwords (TOTP) – generated by apps like Google Authenticator or Authy.
  2. SMS/Email OTP – a code sent to the user’s phone or inbox.
  3. Push notifications – a prompt sent to a mobile app for approval.
  4. Hardware security keys – U2F or WebAuthn devices such as YubiKey.

Choosing the Right Python Library

Several mature libraries simplify MFA implementation. Below is a quick comparison to help you decide:

Library Supported Methods Framework Compatibility Documentation
pyotp TOTP, HOTP Flask, Django, FastAPI, any Comprehensive, examples
django-otp TOTP, YubiKey, SMS Django only Well‑maintained
flask-2fa TOTP, Email OTP Flask only Simple API
python‑webauthn WebAuthn/U2F Any (requires custom integration) Advanced, security‑focused

For most projects, pyotp offers the perfect blend of flexibility and simplicity, especially when you need a cross‑framework solution.

Step‑by‑Step: Implementing TOTP MFA with PyOTP

1. Install the Required Packages

pip install pyotp qrcode[pil] Flask

2. Generate a Secret Key for Each User

The secret key is the shared secret between the server and the authenticator app. Store it securely (e.g., encrypted column in your database).

import pyotp
import os

def generate_secret():
    # 32‑character base32 string – safe for QR code generation
    return pyotp.random_base32()
    
user_secret = generate_secret()
# Save user_secret to the user record in DB

3. Create a QR Code for Easy Enrollment

Most users prefer scanning a QR code rather than typing the secret manually. The following Flask route renders a QR code that can be scanned by Google Authenticator, Authy, or any TOTP app.

from flask import Flask, render_template_string, request, redirect, url_for
import qrcode
import io
import base64

app = Flask(__name__)

@app.route('/mfa/setup')
def mfa_setup():
    secret = user_secret  # retrieve from logged‑in user record
    totp_uri = pyotp.totp.TOTP(secret).provisioning_uri(name='user@example.com', issuer_name='MyApp')
    img = qrcode.make(totp_uri)
    buf = io.BytesIO()
    img.save(buf, format='PNG')
    img_b64 = base64.b64encode(buf.getvalue()).decode('utf-8')
    return render_template_string('''
        

Scan this QR Code with your Authenticator App

MFA QR Code

After scanning, enter the 6‑digit code below to verify.

''', img_data=img_b64)

4. Verify the Token Provided by the User

When the user submits the 6‑digit code, compare it against the server‑generated TOTP value.

@app.route('/mfa/verify', methods=['POST'])
def mfa_verify():
    token = request.form['token']
    secret = user_secret  # fetch from DB again
    totp = pyotp.TOTP(secret)
    if totp.verify(token):
        # Mark MFA as enabled for the user
        return 'MFA setup successful!'
    else:
        return 'Invalid code. Please try again.', 400

5. Enforce MFA on Login

Modify your login flow to check whether the user has MFA enabled. If so, prompt for the TOTP after password verification.

def login(username, password):
    user = get_user(username)
    if not user or not check_password(user, password):
        return 'Invalid credentials', 401

    if user.mfa_enabled:
        # Store user ID in session temporarily and redirect to MFA page
        session['pre_mfa_user_id'] = user.id
        return redirect(url_for('mfa_challenge'))
    else:
        # Regular login without MFA
        session['user_id'] = user.id
        return redirect(url_for('dashboard'))

@app.route('/mfa/challenge', methods=['GET', 'POST'])
def mfa_challenge():
    if request.method == 'POST':
        token = request.form['token']
        user = get_user_by_id(session['pre_mfa_user_id'])
        if pyotp.TOTP(user.mfa_secret).verify(token):
            session['user_id'] = user.id
            session.pop('pre_mfa_user_id')
            return redirect(url_for('dashboard'))
        else:
            return 'Invalid MFA code', 400
    return render_template_string('''
        

Enter your MFA code

''')

Beyond TOTP: Adding SMS or Email OTP

If you need a fallback method for users who don’t have an authenticator app, integrate an SMS or email service. The workflow is similar—generate a random numeric code, send it via the chosen channel, and verify it within a short time window (typically 5‑10 minutes).

  • SMS providers: Twilio, Nexmo, Plivo.
  • Email services: SendGrid, Amazon SES, Mailgun.
  • Store the OTP hash (e.g., SHA‑256) instead of plain text for extra security.

Sample Code for Email OTP

import secrets, hashlib, time
from flask_mail import Mail, Message

mail = Mail(app)

def generate_otp(length=6):
    return ''.join(secrets.choice('0123456789') for _ in range(length))

def send_email_otp(user_email):
    otp = generate_otp()
    # Store a hash with expiration timestamp
    otp_hash = hashlib.sha256(otp.encode()).hexdigest()
    cache.set(f'otp:{user_email}', otp_hash, timeout=300)  # 5 minutes

    msg = Message('Your Login OTP', recipients=[user_email])
    msg.body = f'Your one‑time code is {otp}. It expires in 5 minutes.'
    mail.send(msg)

Best Practices for Secure MFA Implementation

  • Never expose the secret key in URLs, logs, or client‑side code.
  • Rate‑limit verification attempts to mitigate brute‑force attacks.
  • Use HTTPS everywhere—MFA tokens are as sensitive as passwords.
  • Provide backup codes for users who lose their device; store them hashed.
  • Allow MFA reset only after strong identity verification (e.g., support ticket with ID verification).

Comments

Leave a Reply

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