Skip to content

How to Handle 2FA (Two-Factor Authentication) with Python Requests

Learn how to automate Python Requests logins with 2FA using manual code entry or TOTP-based full automation for secure session handling.

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:

  1. Submit username and password
  2. 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 pyotp

Getting 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
Navigation