Skip to main content

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 apps
  • storage_path (str, optional): Path to JSON storage file
  • sq (optional): sqloader instance for SQL storage

Storage Priority:

  1. If sq is provided → SQL storage
  2. If storage_path is provided → JSON storage with custom path
  3. 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/sql directory
  • 🔀 Parameter binding conversion (SQLite :param vs MySQL %s)
  • 🗃️ SQL dialect conversion (PostgreSQL ON CONFLICT vs MySQL ON DUPLICATE KEY UPDATE)
  • 📊 Support for both SELECT and DML statements

When to use:

  • You're using sqloader's SQLiteWrapper or MySQLWrapper directly
  • 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

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

auth2fa-0.1.2.tar.gz (15.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

auth2fa-0.1.2-py3-none-any.whl (24.6 kB view details)

Uploaded Python 3

File details

Details for the file auth2fa-0.1.2.tar.gz.

File metadata

  • Download URL: auth2fa-0.1.2.tar.gz
  • Upload date:
  • Size: 15.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for auth2fa-0.1.2.tar.gz
Algorithm Hash digest
SHA256 cea2b325ec904d9f8eddb24614d46ce12646ed90a8ef310f6a9be79360144a45
MD5 e7bee0d4a6bb474d8ef3a85ad0a59893
BLAKE2b-256 9709f4655770f9e15bc6787d77599e5a03817663f842e5a48cdfadf9b5f61497

See more details on using hashes here.

File details

Details for the file auth2fa-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: auth2fa-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 24.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for auth2fa-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 d53585ea91dc9facaf1693d0d5d036efe33b8bc16b0ce6d0b2999a6ebe1e3ab1
MD5 698696ec5b8c3a423ac5f805411d90fb
BLAKE2b-256 8d564923c217b609eabc313e71ef156ffda63a2fb2419cc9921f2953059b3b79

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page