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.
Metadata
Release files for auth2fa 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| auth2fa-0.1.4.tar.gz | 15.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| auth2fa-0.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.1 kB
Release files / auth2fa-0.1.4.tar.gz
| Download URL | auth2fa-0.1.4.tar.gz |
|---|---|
| Size | 15.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
df740bf713ec564b4afc6d989c087839db60bf69a7155a6b4df4df1f3b2c2883
|
|
BLAKE2b-256 checksum How to use checksums |
a8f152925684d362eca1edd9580e31733ac55fcfbf3d66b9e0379092fb68fae5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.2
|
Release files / auth2fa-0.1.4-py3-none-any.whl
| Download URL | auth2fa-0.1.4-py3-none-any.whl |
|---|---|
| Size | 15.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
114b67e8001e55f597934f7e66d873b5ed4392296257e1c624a3b8fbcfe6bf66
|
|
BLAKE2b-256 checksum How to use checksums |
b47fb17322bc421de94af37f713c7c9a5dc9a89ac0617a90014da3219207a5ac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.2
|
Security for auth2fa 0.1.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.