Skip to content

Authentication — Architecture

Audience: Architects, tech leads, senior engineers evaluating design decisions.

This content was migrated from Documentation/TWO_FACTOR_AUTH.md and restructured into audience sections. Review for accuracy against the current codebase.


Context and Purpose

The Authentication module exists to protect admin-level access to Swisper with a second factor beyond passwords. Admin users can view and modify system configuration, user data, and AI tuning parameters — a compromise would have platform-wide impact. TOTP-based 2FA is a well-understood standard (RFC 6238) that works with any authenticator app, requiring no additional infrastructure like SMS gateways or push notification services.


Architecture Overview

graph TD
    subgraph Setup ["2FA Setup"]
        S1["POST /admin/2fa/setup"] --> S2["Generate TOTP secret\n(pyotp)"]
        S2 --> S3["Generate QR code\n(qrcode SVG)"]
        S3 --> S4["Return secret + QR"]
        S4 --> S5["POST /admin/2fa/verify-setup"]
        S5 --> S6["Verify code\nEncrypt + store secret\nGenerate backup codes"]
    end

    subgraph Login ["Login with 2FA"]
        L1["POST /login/access-token\n(email + password)"] --> L2{"totp_enabled?"}
        L2 -->|"No"| L3["Return full session"]
        L2 -->|"Yes"| L4["Return partial token\n(5 min expiry)"]
        L4 --> L5["POST /login/verify-2fa\n(partial token + code)"]
        L5 --> L6["Verify TOTP or\nbackup code"]
        L6 --> L3
    end

    subgraph Storage ["Data at Rest"]
        DB["User Model\n(PostgreSQL)"]
        ENC["totp_secret\n(PGPString encrypted)"]
        HASH["totp_backup_codes\n(bcrypt hashed)"]
        DB --- ENC
        DB --- HASH
    end

Component Responsibilities

Component Responsibility
2FA Routes (routes.py) API endpoints for setup, verify-setup, and backup code regeneration
2FA Service (service.py) TOTP secret generation, code verification, backup code generation (bcrypt hashing), secret encryption/decryption
Login Route (login.py) Integrates 2FA step into existing login flow; issues partial vs full session tokens
User Model (persistence/models.py) Stores encrypted TOTP secret, enabled flag, backup codes, timestamps

Key Design Decisions

Decision 1: Partial session token for 2FA step

  • Chosen: After password verification, issue a short-lived partial token (5 min) that can only be used for the verify-2fa endpoint.
  • Rejected: Keeping the full session and requiring 2FA before sensitive operations.
  • Rationale: A partial token ensures the user has zero access until 2FA is complete. The 5-minute expiry limits the window for interception.

Decision 2: Backup codes as bcrypt hashes

  • Chosen: Store backup codes as bcrypt hashes; single-use (deleted after use).
  • Rejected: Storing backup codes encrypted (reversible) or not at all.
  • Rationale: bcrypt is irreversible and intentionally slow, making brute-force attacks impractical — even database access doesn't reveal backup codes. Single-use prevents replay attacks.

Known Trade-offs and Debt

  • No rate limiting on TOTP attempts: The totp_last_used_at field exists but rate limiting is not yet implemented. A brute-force attack on the 6-digit code (1M combinations) is theoretically possible without rate limiting, though the 30-second TOTP window limits practical attempts.
  • No audit logging: 2FA enable/disable events are not logged to an audit trail. This makes it harder to investigate unauthorized changes.
  • Admin-only scope: Regular users have no 2FA option. Extending to all users would require frontend changes and a migration strategy for existing sessions.