Aller au contenu

Auth : Challenge MFA à la connexion

Module Beta : opt-in officiel publié sur PyPI depuis 1.0.0-beta.9

forge-mvc-mfa est marqué Development Status :: 4 - Beta (publication PyPI
préparée par MFA-PYPI-READY-001).

Le secret TOTP est chiffré au repos via Fernet (cryptography) avec la clé
FORGE_MFA_SECRET_KEY. Le chiffrement est obligatoire : démarrer sans cette
variable d'environnement lève MfaSecretKeyMissing.

Installation :

pip install --pre forge-mvc-mfa

forge-mvc-mfa n'est pas inclus dans forge-mvc[all] : activer la MFA est
un choix de sécurité explicite de l'application (les extras core couvrent
uniquement RBAC, workflow et stats ; installer le paquet directement). MFA
reste opt-in : le core Forge ne dépend pas de forge-mvc-mfa.

Module extrait : le code MFA vit dans
forge-mvc-mfa. Voir packages/forge-mvc-mfa/README.md pour
l'installation et l'API utilisateur. Cette page documente le flux
de challenge MFA pour mémoire et référence rapide.

Le challenge MFA s'intercale entre la validation du mot de passe et l'ouverture de la session.

Flux général :

mot de passe correct + MFA désactivé  → session ouverte (comportement inchangé)
mot de passe correct + MFA activé     → état temporaire créé → /login/mfa
code MFA valide                        → état temporaire supprimé → session ouverte
code MFA invalide                      → état temporaire conservé → retour formulaire

État temporaire de challenge

Clé de session Rôle
_auth_mfa_user_id Identifiant de l'utilisateur en attente de MFA
_auth_mfa_started_at Timestamp ISO de début (expiration 10 min par défaut)

L'état temporaire n'est pas une session authentifiée. L'utilisateur n'a aucun accès aux routes protégées tant que le code MFA n'est pas validé.

API forge_mvc_mfa

Fonction Comportement
start_mfa_challenge(request, user) Stocke user.id et timestamp en session. Ne connecte pas l'utilisateur. Lève InvalidAuthUserError si utilisateur inactif.
has_pending_mfa_challenge(request, max_age_minutes=10) Retourne True si challenge en cours et non expiré.
get_mfa_challenge_user_id(request) Retourne l'identifiant en attente ou None.
verify_mfa_challenge(request, code, factors, recovery_codes=None) Vérifie code TOTP ou de récupération. Retourne MfaChallengeResult ou None. Supprime le challenge en cas de succès.
clear_mfa_challenge(request) Supprime les clés de challenge. Idempotent.

Exemple d'intégration MVC

# Dans le contrôleur de login, après vérification du mot de passe :
from forge_mvc_mfa import is_mfa_enabled, start_mfa_challenge
from core.auth.user import AuthUser

mfa_factors = get_active_mfa_factors(user_id)
if is_mfa_enabled(mfa_factors):
    auth_user = AuthUser(id=user_id, email=email, password_hash=hash, is_active=True)
    start_mfa_challenge(request, auth_user)
    return redirect("/login/mfa")

# Sinon : ouvrir la session directement

Limites actuelles (AUTH-MFA-004)

Forge ne fournit pas encore dans ce flux : remember device, WebAuthn, SMS, email MFA, gestion des codes de récupération dans le formulaire de connexion.


Référence par module

L'API détaillée est documentée page par page, un fichier par module :

Module Page Contenu
mfa.py Le cœur MFA facteurs, TOTP, challenge, revalidation
recovery.py Les codes de récupération génération et vérification des codes de secours
secret_crypto.py Le chiffrement des secrets chiffrement Fernet du secret TOTP, validation de la clé
totp_replay.py La protection anti-rejeu refus de la réutilisation d'un code TOTP

Politique de stockage des secrets MFA

Statut actuel

forge-mvc-mfa est en Beta. Le secret TOTP est
chiffré au repos via Fernet (bibliothèque cryptography).

Le module est opt-in, non inclus dans forge-mvc[all], et doit être configuré avec
FORGE_MFA_SECRET_KEY avant tout déploiement.

Développement et tests

En développement et en environnement de test isolé :

  • le secret TOTP est chiffré dans auth_mfa_factors.totp_secret (Fernet, préfixe enc:) ;
  • la clé de chiffrement est lue depuis FORGE_MFA_SECRET_KEY, requise même en dev ;
  • les codes de récupération sont stockés sous forme hashée (hash_recovery_code(), SHA-256 + secrets.compare_digest).

Conditions requises même en développement :

  • FORGE_MFA_SECRET_KEY positionné dans l'environnement ;
  • accès à la table auth_mfa_factors limité à l'utilisateur applicatif ;
  • secrets jamais loggés (totp_secret et recovery_code sont dans les champs redactés de sanitize_auth_audit_metadata()) ;
  • base de données non exposée publiquement.

Production

Le module est en Beta. Le chiffrement Fernet est en place (depuis SEC-MFA-SECRET-ENCRYPTION-001).
Certaines exigences avancées (rotation de clé, sauvegarde/restauration, revue
sécurité formelle) restent à la charge de l'application avant un usage critique.

Protection additionnelle recommandée en production :

  • restreindre les droits d'accès à la table auth_mfa_factors au strict minimum applicatif ;
  • stocker FORGE_MFA_SECRET_KEY dans un gestionnaire de secrets (Vault, AWS Secrets Manager…) ;
  • appeler validate_mfa_secret_key_config() au démarrage applicatif (cf.
    Validation au démarrage ci-dessous) ;
  • chiffrement du disque de la base de données ;
  • ne pas exporter auth_mfa_factors dans des dumps non chiffrés ;
  • documenter la procédure de rotation et de sauvegarde/restauration de la clé.

Validation au démarrage

MFA-SECRET-KEY-BOOT-VALIDATION-001 ajoute la fonction
validate_mfa_secret_key_config() qui échoue tôt sur une configuration
dangereuse, plutôt qu'au moment où un utilisateur tente de s'enrôler ou
de se connecter avec MFA.

from forge_mvc_mfa import validate_mfa_secret_key_config

# Au démarrage applicatif (app.py, wsgi.py, ou tout bootstrap équivalent).
validate_mfa_secret_key_config()

Refusé explicitement :

  • FORGE_MFA_SECRET_KEY absente, vide, ou n'ayant que des espaces ;
  • valeurs placeholder évidentes : change-me, changeme, default,
    secret, dev, development, test, testing, placeholder,
    xxx, your-key-here… (insensible à la casse, strippée) ;
  • clé non Fernet (mauvaise longueur ou base64 invalide).

Exceptions levées : MfaSecretKeyMissing, MfaSecretKeyPlaceholder,
MfaSecretInvalidKey. Aucun message ne contient la valeur de la clé
tentée
: pour éviter de fuir un secret dans un log applicatif. Le
message inclut toujours la commande de génération d'une clé valide :

python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

MFA reste opt-in : Forge ne force pas cette validation au niveau du
core. C'est l'application qui décide de l'appeler.

Secrets TOTP

Le secret TOTP est une clé partagée utilisée pour calculer les codes TOTP (RFC 6238).

Pourquoi on ne peut pas simplement hasher le secret TOTP :

Un hash est à sens unique. Pour vérifier un code TOTP, le serveur doit pouvoir recalculer TOTP(secret, timestamp). Si le secret est hashé, cette opération est impossible.

Le stockage production-ready d'un secret TOTP nécessite :

  • un chiffrement applicatif réversible (AES-256-GCM ou équivalent avec clé de chiffrement séparée), ou
  • un HSM (Hardware Security Module), ou
  • un gestionnaire de secrets (Vault, AWS Secrets Manager, ou équivalent).

Depuis SEC-MFA-SECRET-ENCRYPTION-001, forge-mvc-mfa implémente le chiffrement Fernet
(cryptography.fernet.Fernet, AES-128-CBC + HMAC-SHA256) via la clé FORGE_MFA_SECRET_KEY.
Les valeurs stockées en base sont préfixées enc: pour distinguer les secrets chiffrés
d'éventuelles valeurs legacy.

Pour renforcer davantage, coupler FORGE_MFA_SECRET_KEY à un gestionnaire de secrets externe.

Codes de récupération

Les codes de récupération sont correctement protégés dans forge-mvc-mfa (série 1.0.0-beta.x) :

  • générés via secrets.choice() sur un alphabet sans ambiguïté ;
  • hashés avant stockage via hash_recovery_code() (SHA-256) ;
  • vérifiés via secrets.compare_digest() (résistant aux timing attacks) ;
  • stockés en base uniquement sous forme de hash : le code brut n'est jamais persisté.

Cette conception est conforme pour la production, à condition que la base elle-même soit protégée. Un hash de code de récupération exposé ne permet pas de retrouver le code brut.

Exigences avant production-ready

forge-mvc-mfa est en Beta (publié sur PyPI depuis 1.0.0-beta.9). Avant un usage en production critique, l'application doit couvrir les exigences suivantes :

  1. Chiffrement applicatif des secrets TOTP ✓ livré (SEC-MFA-SECRET-ENCRYPTION-001) : Fernet + FORGE_MFA_SECRET_KEY.
  2. Politique de rotation documentée : rotation ou invalidation maîtrisée des secrets compromis.
  3. Documentation de sauvegarde/restauration : procédure en cas de perte de la clé de chiffrement.
  4. Tests dédiés au stockage chiffré ✓ livré (SEC-MFA-SECRET-ENCRYPTION-001) : tests/test_mfa_secret_crypto.py.
  5. Revue sécurité explicite : validation que le stockage chiffré est correct.
  6. Décision explicite de changement de statut ✓ livré (MFA-PYPI-READY-001).
  7. Publication PyPI ✓ livré en 1.0.0-beta.9.
  8. Passage en Beta ✓ acté (tous les opt-ins en Beta).

Tickets liés

Ticket Description État
MFA-SECRET-STORAGE-POLICY-001 Documenter la politique de stockage livré
SEC-MFA-SECRET-ENCRYPTION-001 Chiffrement applicatif du secret TOTP (Fernet) livré
MFA-PYPI-READY-001 Requalification Alpha (Pre-Alpha → Alpha) livré