Understanding Multi-Factor Authentication(MFA)
November 22, 20255 min read
Multi-Factor Authentication (MFA) adds an extra layer of security to your accounts by requiring multiple forms of verification. Instead of just a password, you need something else to prove you're really you.
Authentication factors fall into three categories:
- Something You Know - Passwords, PINs, security questions
- Something You Have - Phone, security token, smart card
- Something You Are - Fingerprint, face recognition, voice
How Time-Based OTP (TOTP) Works
The most common MFA method uses Time-Based One-Time Passwords (TOTP). Here's how it works:
Python Implementation
Let's implement a simple MFA system using TOTP:
Installation
pip install pyotp qrcode[pil]
Basic MFA Implementation
import pyotp
import qrcode
from io import BytesIO
import base64
class MFASystem:
"""Simple Multi-Factor Authentication System"""
def __init__(self):
self.users = {}
def register_user(self, username, password):
"""Register a new user and generate MFA secret"""
# Generate a random secret key
secret = pyotp.random_base32()
# Store user credentials and secret
self.users[username] = {
'password': password, # In production, hash this!
'secret': secret
}
return secret
def get_qr_code(self, username, secret):
"""Generate QR code for authenticator app"""
# Create provisioning URI
uri = pyotp.totp.TOTP(secret).provisioning_uri(
name=username,
issuer_name="MyApp"
)
# Generate QR code
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(uri)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
# Convert to base64 for display
buffer = BytesIO()
img.save(buffer, format='PNG')
img_str = base64.b64encode(buffer.getvalue()).decode()
return img_str
def verify_credentials(self, username, password):
"""Verify username and password (Factor 1)"""
if username not in self.users:
return False
return self.users[username]['password'] == password
def verify_otp(self, username, otp_code):
"""Verify the OTP code (Factor 2)"""
if username not in self.users:
return False
secret = self.users[username]['secret']
totp = pyotp.TOTP(secret)
# Verify OTP with 30-second window
return totp.verify(otp_code, valid_window=1)
def login(self, username, password, otp_code):
"""Complete login with both factors"""
# Factor 1: Password
if not self.verify_credentials(username, password):
return False, "Invalid username or password"
# Factor 2: OTP
if not self.verify_otp(username, otp_code):
return False, "Invalid OTP code"
return True, "Login successful"
# Example Usage
def main():
mfa = MFASystem()
# Step 1: Register a new user
username = "alice"
password = "SecurePassword123"
print("=== User Registration ===")
secret = mfa.register_user(username, password)
print(f"User '{username}' registered successfully!")
print(f"Secret Key: {secret}")
print("Scan this QR code with your authenticator app:")
# Generate QR code (in production, display this as an image)
qr_code = mfa.get_qr_code(username, secret)
print(f"QR Code (base64): {qr_code[:50]}...")
# Step 2: Generate OTP manually (simulating authenticator app)
print("\n=== Login Process ===")
totp = pyotp.TOTP(secret)
current_otp = totp.now()
print(f"Current OTP from authenticator: {current_otp}")
# Step 3: Login with both factors
success, message = mfa.login(username, password, current_otp)
print(f"\nLogin attempt: {message}")
# Test with wrong OTP
print("\n=== Testing Wrong OTP ===")
success, message = mfa.login(username, password, "000000")
print(f"Login attempt: {message}")
if __name__ == "__main__":
main()
Output Example
=== User Registration ===
User 'alice' registered successfully!
Secret Key: JBSWY3DPEHPK3PXP
Scan this QR code with your authenticator app:
QR Code (base64): iVBORw0KGgoAAAANSUhEUgAAAXIAAAFyAQAAAADAX...
=== Login Process ===
Current OTP from authenticator: 847291
Login attempt: Login successful
=== Testing Wrong OTP ===
Login attempt: Invalid OTP code
How the Algorithm Works
The TOTP algorithm generates codes using:
TOTP = HMAC-SHA1(Secret, Time Counter)
Key Points:
- The time counter is
current_unix_time / 30(changes every 30 seconds) - Both server and authenticator use the same algorithm
- They generate the same code because they share the secret key
- Codes expire quickly (30 seconds) for security
Security Best Practices
- Store Secrets Securely: Never store MFA secrets in plain text
- Hash Passwords: Use bcrypt or similar for password storage
- Use HTTPS: Always transmit credentials over secure connections
- Rate Limiting: Limit OTP verification attempts
- Backup Codes: Provide recovery codes in case of device loss
import bcrypt
def hash_password(password):
"""Securely hash a password"""
salt = bcrypt.gensalt()
return bcrypt.hashpw(password.encode(), salt)
def verify_password(password, hashed):
"""Verify a password against its hash"""
return bcrypt.checkpw(password.encode(), hashed)
Common MFA Methods
Conclusion
MFA significantly improves security by requiring multiple verification methods. Even if someone steals your password, they can't access your account without the second factor. The Python implementation above provides a foundation for building MFA into your applications.
Remember: MFA is not unbreakable, but it makes attacks exponentially harder. Combined with other security practices, it's one of the most effective ways to protect user accounts.