Automating logins on websites with Two-Factor Authentication requires special handling. While 2FA is excellent for security, it breaks standard username/password automation. Python offers two main approaches: manual code entry and full automation using TOTP.
Understanding the Challenge
2FA typically works in two steps:
- Submit username and password
- Submit a time-based code from an authenticator app or SMS
The key is maintaining the session between these steps using requests.Session().
Manual Intervention (Interactive Script)
For occasional scripts, pause and ask the user to enter the code:
import requests
session = requests.Session()
login_url = "https://example.com/api/login"
verify_url = "https://example.com/api/2fa"
payload = {"username": "myuser", "password": "mypassword"}
response = session.post(login_url, json=payload)
if response.status_code == 200:
print("Credentials accepted. Awaiting 2FA code...")
code = input("Enter the code sent to your phone: ")
verify_payload = {"code": code}
verify_res = session.post(verify_url, json=verify_payload)
if verify_res.status_code == 200:
print("Login successful!")
protected = session.get("https://example.com/api/protected")
else:
print("Invalid code.")
else:
print("Login failed.")Why Use Session?
import requests
requests.post(login_url, json=credentials)
requests.post(verify_url, json={"code": "123456"}) # Different session!
session = requests.Session()
session.post(login_url, json=credentials)
session.post(verify_url, json={"code": "123456"}) # Same session!Full Automation with TOTP
For autonomous bots and scheduled tasks, generate the 2FA code programmatically using the secret key.
Installation
pip install pyotpGetting the Secret Key
When setting up 2FA, most services show a QR code and a "Manual Entry Key" or "Secret Key", a string like JBSWY3DPEHPK3PXP. Save this securely.
Implementation
import requests
import pyotp
import os
SECRET_KEY = os.environ.get("TOTP_SECRET")
def get_2fa_code():
"""Generate current TOTP code."""
totp = pyotp.TOTP(SECRET_KEY)
return totp.now()
session = requests.Session()
response = session.post("https://example.com/api/login", json={
"username": "bot_user",
"password": "bot_password"
})
if response.status_code == 200:
current_code = get_2fa_code()
print(f"Generated 2FA Code: {current_code}")
verify_response = session.post(
"https://example.com/api/2fa",
json={"code": current_code}
)
if verify_response.status_code == 200:
print("Bot successfully logged in!")
else:
print("2FA verification failed")Handling Timing Issues
TOTP codes are time-based and typically valid for 30 seconds. Handle edge cases:
import pyotp
import time
def get_2fa_code_with_retry(secret_key, max_retries=2):
"""Generate code with retry for timing edge cases."""
totp = pyotp.TOTP(secret_key)
for attempt in range(max_retries):
code = totp.now()
remaining = 30 - (int(time.time()) % 30)
if remaining < 5:
print(f"Code expiring soon, waiting {remaining + 1} seconds...")
time.sleep(remaining + 1)
continue
return code
return totp.now()Complete Example with Error Handling
import requests
import pyotp
import os
from typing import Optional
class TwoFactorAuth:
def __init__(self, base_url: str, secret_key: str):
self.base_url = base_url
self.secret_key = secret_key
self.session = requests.Session()
def login(self, username: str, password: str) -> bool:
"""Complete login flow with 2FA."""
login_response = self.session.post(
f"{self.base_url}/api/login",
json={"username": username, "password": password}
)
if login_response.status_code != 200:
print(f"Login failed: {login_response.text}")
return False
totp = pyotp.TOTP(self.secret_key)
code = totp.now()
verify_response = self.session.post(
f"{self.base_url}/api/2fa",
json={"code": code}
)
if verify_response.status_code != 200:
print(f"2FA failed: {verify_response.text}")
return False
print("Successfully authenticated!")
return True
def get(self, endpoint: str) -> Optional[requests.Response]:
"""Make authenticated GET request."""
return self.session.get(f"{self.base_url}{endpoint}")
auth = TwoFactorAuth(
base_url="https://example.com",
secret_key=os.environ["TOTP_SECRET"]
)
if auth.login("username", "password"):
response = auth.get("/api/protected-data")
print(response.json())Summary
| Method | Best For | Requirements |
|---|---|---|
| Manual Input | Occasional scripts, debugging | Phone access |
| PyOTP (Automated) | Servers, cron jobs, bots | 2FA Secret Key |
Key Points:
- Always use
requests.Session()to maintain cookies between login steps - Store TOTP secrets securely using environment variables
- Handle timing edge cases for codes near expiration
- The secret key is shown once during 2FA setup; save it then