TOTP-based Two-Factor Authentication Library for Python
Project description
auth2fa
A flexible and easy-to-use Python library for implementing TOTP-based two-factor authentication with support for multiple storage backends.
Features
- ✅ TOTP-based 2FA (Time-based One-Time Password)
- ✅ QR code generation for easy setup
- ✅ Recovery codes (8 codes, single-use)
- ✅ Multiple storage backends (JSON file / SQL database)
- ✅ Storage interface for custom backends
- ✅ sqloader integration (optional)
- ✅ No async dependencies - pure synchronous implementation
- ✅ Compatible with Google Authenticator, Authy, etc.
Installation
Basic Installation (JSON storage only)
pip install auth2fa
With SQL Storage Support
pip install auth2fa[sql]
Quick Start
JSON Mode (Default)
from auth2fa import TwoFactorAuth
# Initialize with JSON storage (default)
tfa = TwoFactorAuth(issuer="MyApp")
# Setup 2FA for a user
result = tfa.setup("user123", username="john@example.com")
print(result["qr_uri"]) # otpauth:// URI for QR code
print(result["qr_image"]) # Base64 encoded PNG image
print(result["recovery_codes"]) # ['A3F8K2M1', 'B7D2N4P9', ...]
# Activate 2FA (user enters code from authenticator app)
if tfa.activate("user123", "482901"):
print("2FA activated!")
# Verify during login
if tfa.verify("user123", "173829"):
print("Login successful!")
SQL Mode (with sqloader)
Option 1: Using sqloader directly (if sqloader supports auth2fa's execute interface)
from auth2fa import TwoFactorAuth
from sqloader import SQLite3 # or MySQL
# Initialize database
sq = SQLite3("database.db", sql_path="./auth2fa/sql")
# Initialize with SQL storage
tfa = TwoFactorAuth(sq=sq, issuer="FileForge")
# Rest of the API is the same
result = tfa.setup("user123", username="shin")
tfa.activate("user123", "482901")
tfa.verify("user123", "173829")
Option 2: Using Auth2FAAdapter (for sqloader's raw database instances)
from auth2fa import TwoFactorAuth, Auth2FAAdapter
from sqloader import SQLiteWrapper # or MySQLWrapper
# Initialize sqloader database instance
db = SQLiteWrapper('database.db')
# Wrap with adapter
adapter = Auth2FAAdapter(db)
# Initialize with SQL storage
tfa = TwoFactorAuth(sq=adapter, issuer="MyApp")
# Use normally
result = tfa.setup("user123", username="john@example.com")
tfa.activate("user123", "482901")
Custom Storage Path
# Use custom JSON file path
tfa = TwoFactorAuth(storage_path="./data/2fa.json", issuer="MyApp")
API Reference
TwoFactorAuth
__init__(issuer="MyApp", storage_path=None, sq=None)
Initialize TwoFactorAuth instance.
Parameters:
issuer(str): Application name displayed in authenticator appsstorage_path(str, optional): Path to JSON storage filesq(optional): sqloader instance for SQL storage
Storage Priority:
- If
sqis provided → SQL storage - If
storage_pathis provided → JSON storage with custom path - Otherwise → JSON storage at
./totp_data.json
setup(user_id, username="")
Set up TOTP for a user.
Returns: dict
{
"secret": "BASE32SECRET",
"qr_uri": "otpauth://totp/...",
"qr_image": "base64encodedPNG...",
"recovery_codes": ["A3F8K2M1", ...]
}
activate(user_id, code)
Activate TOTP after user verifies the code from their authenticator app.
Returns: bool (True if successful)
verify(user_id, code)
Verify TOTP code during login.
Behavior:
- Unconfigured users → returns
True(pass through) - Verifies TOTP code first
- If TOTP fails, automatically tries recovery code
- Recovery codes are single-use (automatically removed)
Returns: bool
disable(user_id)
Completely remove TOTP configuration for a user.
is_enabled(user_id)
Check if TOTP is enabled for a user.
Returns: bool
regenerate_recovery_codes(user_id)
Generate new recovery codes (invalidates old ones).
Returns: list
Integration Example
Login Flow with 2FA
from auth2fa import TwoFactorAuth
tfa = TwoFactorAuth(issuer="MyApp")
@router.post("/login")
async def login(credentials):
user = authenticate(credentials)
if tfa.is_enabled(str(user.id)):
return {"status": "totp_required", "temp_token": create_temp_token(user)}
return {"access_token": create_jwt(user)}
@router.post("/login/verify-totp")
async def verify_totp(temp_token, code):
user = validate_temp_token(temp_token)
if tfa.verify(str(user.id), code):
return {"access_token": create_jwt(user)}
return {"error": "invalid_code"}
@router.post("/user/setup-2fa")
async def setup_2fa(user):
result = tfa.setup(str(user.id), username=user.email)
return {
"qr_image": result["qr_image"], # Display as <img src="data:image/png;base64,..." />
"recovery_codes": result["recovery_codes"] # Show to user (print/save)
}
@router.post("/user/activate-2fa")
async def activate_2fa(user, code):
if tfa.activate(str(user.id), code):
return {"success": True}
return {"error": "invalid_code"}
@router.post("/user/disable-2fa")
async def disable_2fa(user):
tfa.disable(str(user.id))
return {"success": True}
Storage Backends
JSON Storage (Default)
- File-based storage with file locking
- Default path:
./totp_data.json - Thread-safe with lock file mechanism
- Auto-converts user_id to string
SQL Storage (with sqloader)
Requires auth2fa[sql] installation.
Supported Databases:
- SQLite3
- MySQL
- PostgreSQL (via sqloader)
Table Schema:
CREATE TABLE totp_auth (
user_id VARCHAR(255) PRIMARY KEY,
secret VARCHAR(255) NOT NULL,
enabled BOOLEAN DEFAULT FALSE,
recovery_codes TEXT,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Indexes
CREATE INDEX idx_totp_auth_enabled ON totp_auth(enabled);
Database Compatibility:
- ✅ SQLite3
- ✅ MySQL / MariaDB
- ✅ PostgreSQL
Auth2FAAdapter
The Auth2FAAdapter is an optional adapter that bridges auth2fa with sqloader's database instances. It's automatically available when sqloader is installed.
Features:
- 🔄 Automatic SQL file loading from
auth2fa/sqldirectory - 🔀 Parameter binding conversion (SQLite
:paramvs MySQL%s) - 🗃️ SQL dialect conversion (PostgreSQL
ON CONFLICTvs MySQLON DUPLICATE KEY UPDATE) - 📊 Support for both SELECT and DML statements
When to use:
- You're using sqloader's
SQLiteWrapperorMySQLWrapperdirectly - You need automatic database dialect conversion
- You want to avoid sqloader dependency in auth2fa core
Example:
from auth2fa import Auth2FAAdapter
from sqloader import SQLiteWrapper
# Your existing sqloader setup
db = SQLiteWrapper('app.db')
# Wrap it for auth2fa
adapter = Auth2FAAdapter(db)
# Use with TwoFactorAuth
from auth2fa import TwoFactorAuth
tfa = TwoFactorAuth(sq=adapter, issuer="MyApp")
Without sqloader:
If sqloader is not installed, Auth2FAAdapter won't be available, but auth2fa will still work with JSON storage:
from auth2fa import TwoFactorAuth
# Falls back to JSON storage
tfa = TwoFactorAuth(issuer="MyApp") # Uses ./totp_data.json
Custom Storage Backend
Implement BaseStorage interface:
from auth2fa.storage.base import BaseStorage
class CustomStorage(BaseStorage):
def save(self, user_id: str, data: dict) -> None:
# Your implementation
pass
def get(self, user_id: str) -> dict | None:
# Your implementation
pass
def delete(self, user_id: str) -> None:
# Your implementation
pass
def exists(self, user_id: str) -> bool:
# Your implementation
pass
# Use custom storage
from auth2fa import TwoFactorAuth
tfa = TwoFactorAuth.__new__(TwoFactorAuth)
tfa.issuer = "MyApp"
tfa.storage = CustomStorage()
Data Structure
TOTP data stored for each user:
{
"secret": "BASE32SECRET", # TOTP secret key
"enabled": False, # Activation status
"recovery_codes": [ # Single-use backup codes
"A3F8K2M1",
"B7D2N4P9",
# ... 8 codes total
],
"created_at": "2026-02-11T18:00:00" # ISO format timestamp
}
Security Notes
- Recovery codes are alphanumeric (excluding 0, O, I, 1 for clarity)
- TOTP uses 30-second time window with ±1 window tolerance
- Recovery codes are case-insensitive
- Each recovery code can only be used once
- File locking prevents race conditions in JSON storage
Requirements
- Python 3.10+
- pyotp
- qrcode
- Pillow
Optional:
- sqloader (for SQL storage)
License
MIT License
Links
- Homepage: https://github.com/horrible-gh/auth2fa
- Bug Tracker: https://github.com/horrible-gh/auth2fa/issues
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file auth2fa-0.1.4.tar.gz.
File metadata
- Download URL: auth2fa-0.1.4.tar.gz
- Upload date:
- Size: 15.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
df740bf713ec564b4afc6d989c087839db60bf69a7155a6b4df4df1f3b2c2883
|
|
| MD5 |
30d0a8247a81a0538217b92bc7310edc
|
|
| BLAKE2b-256 |
a8f152925684d362eca1edd9580e31733ac55fcfbf3d66b9e0379092fb68fae5
|
File details
Details for the file auth2fa-0.1.4-py3-none-any.whl.
File metadata
- Download URL: auth2fa-0.1.4-py3-none-any.whl
- Upload date:
- Size: 15.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
114b67e8001e55f597934f7e66d873b5ed4392296257e1c624a3b8fbcfe6bf66
|
|
| MD5 |
746cffc38a02e025d42aaf709dbc6006
|
|
| BLAKE2b-256 |
b47fb17322bc421de94af37f713c7c9a5dc9a89ac0617a90014da3219207a5ac
|