Skip to main content

mfa-chain-orchestrator

Policy-driven MFA chain orchestration with strict step order, TOTP verification, and reset-on-failure behavior.

Features

  • fixed or random MFA factor sequencing per attempt.
  • Enforced required_steps with validated policy definitions.
  • TOTP verification via pyotp.
  • Strict ordering protection via MFAChainBreached.
  • Security hardening: any single failure returns RESET and forces restart from Token 1.

Installation

pip install -r requirements.txt

or

pip install .

Usage

from mfa_chain_orchestrator import MFAOrchestrator, Policy

policy = Policy(
    mode="random",
    required_steps=2,
    factors=[
        {"id": "token_1", "label": "Authenticator App", "type": "totp"},
        {"id": "token_2", "label": "Backup Device", "type": "totp"},
        {"id": "token_3", "label": "Hardware Token", "type": "totp"},
    ],
)

orchestrator = MFAOrchestrator(policy)
chain = orchestrator.initialize_attempt()
first = chain[0]

# Verify step 1
result = orchestrator.verify_step(
    secret="JBSWY3DPEHPK3PXP",  # Base32 secret
    user_input="123456",
    window=1,
    factor_id=first.id,
)

if not result.success and result.next_factor_label == "RESET":
    # Restart from Token 1 (call initialize_attempt again if desired)
    pass

FastAPI Session Integration Example

from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel

from mfa_chain_orchestrator import MFAChainBreached, MFAOrchestrator, Policy

app = FastAPI()

policy = Policy(
    mode="fixed",
    required_steps=2,
    factors=[
        {"id": "token_1", "label": "Token 1", "type": "totp"},
        {"id": "token_2", "label": "Token 2", "type": "totp"},
    ],
)

orchestrator_store: dict[str, MFAOrchestrator] = {}


class StepVerifyPayload(BaseModel):
    factor_id: str
    user_input: str


def get_orchestrator(session_id: str) -> MFAOrchestrator:
    if session_id not in orchestrator_store:
        orchestrator = MFAOrchestrator(policy)
        chain = orchestrator.initialize_attempt()
        # Persist chain metadata in your server-side session store if needed.
        orchestrator_store[session_id] = orchestrator
    return orchestrator_store[session_id]


@app.post("/mfa/verify")
def verify_step(payload: StepVerifyPayload, request: Request):
    session_id = request.headers.get("X-Session-Id")
    if not session_id:
        raise HTTPException(status_code=400, detail="Missing session id")

    orchestrator = get_orchestrator(session_id)

    try:
        result = orchestrator.verify_step(
            secret="JBSWY3DPEHPK3PXP",  # fetch per-user secret from secure storage
            user_input=payload.user_input,
            window=1,
            factor_id=payload.factor_id,
        )
    except MFAChainBreached as exc:
        raise HTTPException(status_code=409, detail=str(exc)) from exc

    if not result.success and result.next_factor_label == "RESET":
        # Any failure hard-resets chain state.
        orchestrator.initialize_attempt()
        raise HTTPException(status_code=401, detail="MFA reset. Start again from Token 1.")

    if result.is_complete:
        return {"authenticated": True}

    return {
        "authenticated": False,
        "next_factor_label": result.next_factor_label,
    }

Security Notes

  • verify_step() validates code shape (6 digits) before TOTP verification.
  • On any failure, cursor resets to the first step and returns next_factor_label="RESET".
  • Out-of-order calls can be blocked by passing factor_id; mismatches raise MFAChainBreached.
  • Store secrets in an HSM/KMS-backed vault, never in plaintext config.

Public API

  • MFAOrchestrator(policy: Policy)
  • MFAOrchestrator.initialize_attempt() -> list[FactorDefinition]
  • MFAOrchestrator.verify_step(secret: str, user_input: str, window: int, factor_id: str | None = None) -> Result
  • MFAChainBreached
  • Policy, FactorDefinition, Result

Runnable Test App (FastAPI)

A ready-to-run test app is included at examples/fastapi_test_app.py.

1. Install extra test-app dependencies

pip install fastapi uvicorn

Optional QR PNG payload support:

pip install qrcode[pil]

2. Run the app

PYTHONPATH=src uvicorn examples.fastapi_test_app:app --reload

Open:

  • http://127.0.0.1:8000/ (or http://127.0.0.1:8000/ui)

The UI guides users through:

  • enrolling Google and Microsoft authenticator factors
  • starting a login attempt
  • verifying each MFA step in order

4. API-first test flow (curl)

4. Test the flow

Enroll two different authenticator apps for one user:

curl -s -X POST http://127.0.0.1:8000/users/alice/factors/google_auth/enroll
curl -s -X POST http://127.0.0.1:8000/users/alice/factors/ms_auth/enroll

Then start an attempt:

curl -s -X POST http://127.0.0.1:8000/users/alice/attempt/start

Get the currently expected factor + a debug code:

curl -s http://127.0.0.1:8000/attempt/<session_id>/debug/current-code

Verify:

curl -s -X POST http://127.0.0.1:8000/attempt/verify \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "<session_id>",
    "factor_id": "<expected factor_id>",
    "user_input": "<code>",
    "window": 1
  }'

If verification fails, the orchestrator returns reset behavior and the chain restarts from step 1. For the full scriptable process, see TEST_PROCESS.md.

License

This project is licensed under the GNU General Public License v3.0. See LICENSE.

Metadata

Release files for mfa-chain-orchestrator 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mfa-chain-orchestrator 0.1.0
File Size Uploaded Provenance
mfa_chain_orchestrator-0.1.0.tar.gz 17.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mfa-chain-orchestrator 0.1.0
File Interpreter ABI Platform
mfa_chain_orchestrator-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 36.4 kB

Release files / mfa_chain_orchestrator-0.1.0.tar.gz

Download URL mfa_chain_orchestrator-0.1.0.tar.gz
Size 17.7 kB
Tags Source
SHA-256 checksum
How to use checksums
2a21256be84b5bdc6fd19d13aa6aa03c4de346bf4670bfd8bdaf33a728cf8c67
BLAKE2b-256 checksum
How to use checksums
517556ca7863e1a965dd23ac5ce6988427042db72767fd04f2045457a8fbb1c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 22, 2026.

Transparency log

Release files / mfa_chain_orchestrator-0.1.0-py3-none-any.whl

Download URL mfa_chain_orchestrator-0.1.0-py3-none-any.whl
Size 18.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4174c6fc8010e30d8fc45acea834a8e12e3edd40be9a680d283bbe0fd1b672b9
BLAKE2b-256 checksum
How to use checksums
f404266b6fdc99d41102f00e3961db467c71d215ababdf3e14c6ca54d217b7b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 22, 2026.

Transparency log

Security for mfa-chain-orchestrator 0.1.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mfa_chain_orchestrator-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 22, 2026.

Transparency log

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page