MFA Implementation in Python — Core Concepts
Why passwords aren’t enough
Password breaches are routine. The “Have I Been Pwned” database contains over 12 billion compromised accounts. People reuse passwords, fall for phishing, and choose weak ones. MFA adds a second barrier: even with a stolen password, an attacker needs physical access to the user’s device.
Google reported that adding MFA blocked 100% of automated bot attacks, 99% of bulk phishing attacks, and 76% of targeted attacks. That’s why most security frameworks (NIST, SOC 2, PCI-DSS) now require or strongly recommend MFA.
Authentication factors
MFA combines two or more factors from different categories:
Knowledge — something the user knows (password, PIN, security question).
Possession — something the user has (phone, hardware key, smart card).
Inherence — something the user is (fingerprint, face, voice).
Using two from the same category (like password + security question) is technically not MFA — both are knowledge factors.
TOTP: Time-based One-Time Passwords
TOTP (RFC 6238) is the most common MFA method. It works like this:
- During setup, the server generates a random shared secret (a 160-bit key)
- The user scans a QR code that encodes this secret into their authenticator app
- Both server and app use the same formula:
HMAC-SHA1(secret, floor(time / 30))to produce a 6-digit code - The code changes every 30 seconds
- At login, the user types the current code, and the server verifies it matches
Because the calculation is time-based, server and phone must have roughly synchronized clocks. Most implementations accept codes from the previous and next 30-second windows to account for slight drift.
Setup flow
The typical MFA enrollment flow:
- User enables MFA in account settings
- Server generates a random secret and stores it (encrypted) with the user record
- Server shows a QR code containing a
otpauth://URI - User scans QR code with authenticator app (Google Authenticator, Authy, 1Password)
- User enters the current code to confirm setup works
- Server enables MFA for the account and provides backup codes
Backup codes
If a user loses their phone, they need a way back in. Backup codes are a set of single-use codes (typically 8-10) generated during MFA setup. Each code works once. Users should store these securely — printed and locked away, or in a password manager.
Python implementation with pyotp
import pyotp
# Generate a random secret for a user
secret = pyotp.random_base32() # e.g., "JBSWY3DPEHPK3PXP"
# Generate the provisioning URI for QR code
uri = pyotp.totp.TOTP(secret).provisioning_uri(
name="user@example.com",
issuer_name="MyApp",
)
# otpauth://totp/MyApp:user%40example.com?secret=JBSWY3DPEHPK3PXP&issuer=MyApp
# Verify a code submitted by the user
totp = pyotp.TOTP(secret)
is_valid = totp.verify("123456") # True if code matches current window
Common misconception
“SMS-based MFA is just as good as authenticator apps.” SMS codes are vulnerable to SIM swapping attacks, where an attacker convinces the carrier to transfer the victim’s phone number. NIST deprecated SMS as an MFA method in 2016 (SP 800-63B). TOTP apps and hardware security keys are significantly more resistant to remote attacks.
The one thing to remember: TOTP-based MFA uses a shared secret and the current time to generate matching codes on both server and phone — adding a possession factor that makes stolen passwords useless without the user’s device.
See Also
- Python Api Key Management Why apps use special passwords called API keys, and how to keep them safe — explained with a library card analogy
- Python Attribute Based Access Control How apps make fine-grained permission decisions based on who you are, what you're accessing, and the circumstances — explained with an airport analogy
- Python Audit Logging Learn Audit Logging with a clear mental model so your Python code is easier to trust and maintain.
- Python Bandit Security Scanning Why Bandit Security Scanning helps Python teams catch painful mistakes early without slowing daily development.
- Python Clickjacking Prevention How invisible website layers trick you into clicking the wrong thing, and how Python apps stop it