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:

  1. During setup, the server generates a random shared secret (a 160-bit key)
  2. The user scans a QR code that encodes this secret into their authenticator app
  3. Both server and app use the same formula: HMAC-SHA1(secret, floor(time / 30)) to produce a 6-digit code
  4. The code changes every 30 seconds
  5. 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:

  1. User enables MFA in account settings
  2. Server generates a random secret and stores it (encrypted) with the user record
  3. Server shows a QR code containing a otpauth:// URI
  4. User scans QR code with authenticator app (Google Authenticator, Authy, 1Password)
  5. User enters the current code to confirm setup works
  6. 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.

pythonsecurityauthenticationweb

See Also