L'authentification multi-facteurs dans Forge (forge-mvc-mfa)¶
Ce document explique ce que fait l'opt-in forge-mvc-mfa, ce qu'il expose, et comment on s'en sert.
Module extrait
Le code MFA a été extrait du cœur vers le paquet forge-mvc-mfa ; le cœur Forge n'en dépend pas.
Cet opt-in suit la version du cœur
Il n'a pas de cycle de maturité propre : sa version est celle du pyproject.toml racine (OPTINS-MATURITY-FOLLOWS-CORE-001).
Le secret TOTP est chiffré au repos via Fernet (SEC-MFA-SECRET-ENCRYPTION-001), et sa clé se tourne depuis MFA-KEY-ROTATION-001.
forge-mvc-mfa ajoute un second facteur d'authentification : TOTP (application d'authentification), codes de récupération, challenge à la connexion, revalidation et protections (anti-rejeu, rate-limit).
Le secret TOTP est chiffré au repos (Fernet) ; l'application décide où persister les facteurs et quand exiger le second facteur.
Clé de chiffrement obligatoire
Le secret TOTP est chiffré avec FORGE_MFA_SECRET_KEY (Fernet).
Démarrer sans cette variable lève MfaSecretKeyMissing : le chiffrement n'est pas optionnel.
1. Rôle du module
Le mot de passe seul ne suffit pas pour les actions sensibles.
L'opt-in ajoute un second facteur.
Il couvre quatre temps :
- enrôlement : générer un secret TOTP, l'afficher en QR Code, confirmer le premier code ;
- challenge : après le mot de passe, exiger un code TOTP avant d'ouvrir la session ;
- revalidation : redemander le facteur avant une action critique (step-up) ;
- récupération : des codes à usage unique si l'appareil TOTP est perdu.
Forge fournit les helpers et les contrats ; la persistance des facteurs et des codes reste applicative (ADR-008).
2. Installation
Prérequis : activez le venv du projet
Quelle que soit la source, installez dans le venv du projet :
Lancé hors d'un venv, pip vise le Python système (Debian 12+, Ubuntu 23.04+),
protégé par PEP 668. Il refuse alors d'installer, pour ne pas écraser les paquets
gérés par apt, et affiche externally-managed-environment.
Le venv de projet créé par forge new n'a pas ce verrou.
Installer le paquet¶
B. Depuis Git (avant-garde)¶
Cœur puis opt-in depuis git, dans le venv du projet (l'opt-in trouve le cœur git déjà en place, sans version publiée sur PyPI) :
3. Mise en service
Installer le paquet ne suffit pas à le rendre opérationnel.
Voici les gestes propres à forge-mvc-mfa, dans l'ordre.
Ils déclinent la procédure canonique, Rendre un opt-in opérationnel : les cinq
points.
1. L'épingler¶
Dans requirements.txt, à la même version ou au même commit que forge-mvc.
Sans cette ligne, l'opt-in n'existe que sur votre machine.
2. L'inscrire¶
L'opt-in est inscrit dans optins/registry.py (ADR-061), ce qui le rend visible du
projet.
--apply est obligatoire : sans lui, la commande simule et n'écrit rien.
3. Poser ce dont il a besoin¶
Rien à faire dans le cas courant.
Cet opt-in n'apporte aucune table, la persistance des facteurs appartenant à l'application.
Une seule exception, si vous servez l'authentification par plusieurs workers et voulez
un anti-rejeu TOTP commun à tous.
Le registre partagé, décrit plus bas, s'appuie alors sur une table.
forge mfa:init # écrit la migration dans mvc/migrations/, sans l'exécuter
forge migration:apply # après relecture
La déclaration de cette table vit dans tables.py, rendue pour le backend installé.
4. Le brancher là où il agit¶
Il se branche dans app.py, là où l'application compose ses middlewares et ses
fournisseurs de contexte. Ce câblage vous appartient : Forge ne l'écrit jamais à
votre place (principe 9).
5. Le prouver¶
Puis un premier usage réel.
Un opt-in installé, inscrit et provisionné qu'aucun code n'appelle n'est pas
opérationnel : il est seulement présent.
4. Désinstallation
opt-in:disable est l'inverse d'enable : il dé-inscrit du registre, sans toucher au paquet.
forge opt-in:remove mfa affiche la commande pip uninstall sans l'exécuter.
5. Commandes
Cet opt-in n'expose aucune commande CLI : il s'utilise par import dans le code applicatif (voir l'API publique ci-dessous).
6. Vue d'ensemble rapide
| Élément | Valeur |
|---|---|
| Paquet | forge-mvc-mfa |
| Module | forge_mvc_mfa |
| Catégorie | Sécurité et accès (ADR-055) |
| Couche | opt-in (brique optionnelle), transversal au flux d'auth |
| Dépend de | forge-mvc, pyotp, cryptography (Fernet) |
| Facteurs | TOTP (MFA_FACTOR_TOTP), codes de récupération (MFA_FACTOR_RECOVERY) |
| Chiffrement | secret TOTP chiffré (Fernet), clé FORGE_MFA_SECRET_KEY |
| Protections | anti-rejeu TOTP, rate-limit du challenge et de la revalidation |
| API publique | enrôlement, challenge, revalidation, codes de récupération, chiffrement |
| Persistance | applicative (ADR-008) : AuthMfaFactor, codes de récupération |
| Installation | pip install --pre forge-mvc-mfa |
7. Schémas UML
Les deux schémas suivants montrent deux vues complémentaires de l'opt-in.
Le diagramme de classe montre les groupes d'API et le secret chiffré.
Le diagramme de séquence montre le challenge MFA à la connexion.
5.1 Diagramme de classe¶
Le diagramme de classe montre les fonctions groupées par rôle, le facteur persisté et le chiffrement du secret.
classDiagram
direction LR
class enrolment {
<<module>>
+generate_totp_secret() str
+create_totp_factor(...)
+confirm_totp_factor(...) AuthMfaFactor
+totp_provisioning_uri(...) str
+verify_totp_code(...)
}
class challenge {
<<module>>
+start_mfa_challenge(...)
+verify_mfa_challenge(...)
+require_recent_mfa(...)
+verify_mfa_revalidation(...)
}
class recovery {
<<module>>
+create_recovery_codes(...)
+consume_recovery_code(...)
}
class secret_crypto {
<<module>>
+encrypt_totp_secret(...)
+decrypt_totp_secret(...)
+validate_mfa_secret_key_config()
}
class AuthMfaFactor {
<<dataclass>>
+user_id
+type
+status
+secret_chiffré
}
enrolment --> AuthMfaFactor : produit
enrolment --> secret_crypto : chiffre le secret
challenge --> AuthMfaFactor : vérifie
recovery --> AuthMfaFactor : alternative TOTP
À retenir :
- l'enrôlement produit un
AuthMfaFactor(secret chiffré) ; - le challenge vérifie un code TOTP ou un code de récupération ;
- la revalidation rejoue le facteur avant une action critique ;
- le secret n'est jamais stocké en clair (Fernet).
5.2 Diagramme de séquence¶
Le diagramme de séquence montre le challenge à la connexion, après le mot de passe.
sequenceDiagram
actor Utilisateur
participant Login as Contrôleur login
participant MFA as forge_mvc_mfa
participant Session as Session
Utilisateur->>Login: identifiants (mot de passe OK)
Login->>MFA: is_mfa_enabled(user) ?
alt MFA actif
Login->>MFA: start_mfa_challenge(user)
Login-->>Utilisateur: demande le code TOTP
Utilisateur->>Login: code à 6 chiffres
Login->>MFA: verify_mfa_challenge(code)
MFA-->>Login: succès (ou code de récupération)
Login->>Session: ouvre la session
else MFA inactif
Login->>Session: ouvre la session directement
end
À retenir :
- le challenge intervient après la vérification du mot de passe ;
- la session n'est ouverte qu'une fois le second facteur validé ;
- un code de récupération est une alternative au code TOTP ;
- le challenge est limité en tentatives et en durée (anti-bruteforce).
8. API publique
Secret et chiffrement¶
| Élément | Rôle |
|---|---|
generate_totp_secret() -> str |
génère un secret TOTP |
encrypt_totp_secret / decrypt_totp_secret |
chiffre/déchiffre le secret (Fernet) |
validate_mfa_secret_key_config() -> None |
vérifie FORGE_MFA_SECRET_KEY au démarrage |
rotate_totp_secret(stored) -> str |
rechiffre un secret avec la clé courante |
uses_current_key(stored) -> bool |
dit si un secret dépend encore d'une clé retirée |
previous_keys() -> list[str] |
clés retirées déclarées, dans l'ordre |
Enrôlement TOTP¶
| Élément | Rôle |
|---|---|
create_totp_factor(...) |
crée un facteur TOTP en attente |
confirm_totp_factor(...) -> AuthMfaFactor |
confirme avec le premier code |
totp_provisioning_uri(...) -> str |
URI otpauth:// (pour QR Code) |
verify_totp_code(...) |
vérifie un code TOTP |
TotpSetup, AuthMfaFactor |
données d'enrôlement et facteur |
Challenge et revalidation¶
| Élément | Rôle |
|---|---|
start_mfa_challenge(...) |
démarre le challenge (état en session) |
verify_mfa_challenge(...) |
vérifie le code du challenge |
has_pending_mfa_challenge, clear_mfa_challenge |
état du challenge |
require_recent_mfa(...) |
exige une revalidation récente (step-up) |
mark_mfa_revalidated, verify_mfa_revalidation |
revalidation |
Codes de récupération¶
| Élément | Rôle |
|---|---|
create_recovery_codes(...) |
génère des codes à usage unique |
consume_recovery_code(...) |
consomme un code (irréversible) |
Constantes¶
MFA_FACTOR_TOTP, MFA_FACTOR_RECOVERY, MFA_STATUS_ACTIVE / PENDING / DISABLED, fenêtres et tentatives du challenge et de la revalidation.
9. Contextes d'utilisation
| Besoin | Élément |
|---|---|
| Vérifier la clé au démarrage | validate_mfa_secret_key_config() |
| Enrôler un utilisateur | create_totp_factor + totp_provisioning_uri + confirm_totp_factor |
| Fermer les sessions ouvertes à l'activation | store.delete_for_user(...) du cœur |
| Exiger le 2e facteur au login | start_mfa_challenge + verify_mfa_challenge |
| Protéger une action sensible | require_recent_mfa(...) |
| Fournir un secours | create_recovery_codes / consume_recovery_code |
10. Exemple : challenge à la connexion
from forge_mvc_mfa import (
is_mfa_enabled, start_mfa_challenge, verify_mfa_challenge,
)
# Après vérification du mot de passe. `factors` vient de votre stockage :
# c'est la liste des facteurs MFA de cet utilisateur.
if is_mfa_enabled(factors):
start_mfa_challenge(request, user)
return redirect("/login/mfa") # demander le code TOTP
else:
open_session(request, user) # pas de MFA : session directe
# Sur la page de saisie du code :
if verify_mfa_challenge(request, request.form("code"), factors):
open_session(request, user)
else:
return Response.text("Code invalide", status=401)
factors est demandé partout où la décision en dépend, plutôt que rechargé en interne : Forge ne va pas chercher vos données à votre place, et le SQL de leur lecture reste chez vous (principe 5).
Aide-mémoire
Quatre temps, une clé de chiffrement :
- enrôler (secret + QR + confirmation) ;
- challenger au login ;
- revalider avant le sensible ;
- récupérer via codes à usage unique.
11. Fermer les sessions ouvertes à l'activation
Activer un second facteur ne protège rien tant que les sessions ouvertes avant l'activation restent valides.
Un accès obtenu avec le seul mot de passe survivrait au renforcement, ce qui vide le geste de son sens.
Le cœur fournit la primitive, et l'application l'appelle après avoir enregistré le facteur confirmé.
from core.sessions.access import get_session_id
from core.sessions.manager import get_session_store
from forge_mvc_mfa import confirm_totp_factor
actif = confirm_totp_factor(facteur, code_saisi)
if actif is None:
return rendre_erreur("Code invalide.")
enregistrer_facteur(actif)
# Les autres sessions tombent, celle qui vient d'activer est épargnée.
get_session_store().delete_for_user(
actif.user_id, except_session_id=get_session_id(request)
)
Épargner la session courante n'est pas facultatif
Sans except_session_id, l'utilisateur qui vient d'activer son facteur est déconnecté par son propre geste.
Cela ne protège de rien, et transforme un renforcement en panne apparente.
Pourquoi l'opt-in ne le fait pas lui-même
confirm_totp_factor est une fonction pure.
Elle ne touche ni la base ni les sessions, et rend un nouveau facteur sans modifier l'original.
L'appel reste donc explicite, à l'endroit où l'application enregistre le facteur.
Forge refuse la magie cachée (principe 3), et un opt-in qui fermerait des sessions à l'insu de l'appelant en serait.
12. Sécurité des secrets
Le secret TOTP est chiffré au repos avec Fernet (cryptography) et la clé FORGE_MFA_SECRET_KEY ; il n'est jamais stocké en clair.
Appelez validate_mfa_secret_key_config() au démarrage (app.py / wsgi.py) : démarrer sans clé valide échoue tôt plutôt qu'à la première écriture.
Codes de récupération à usage unique
Les codes de récupération sont stockés hachés et consommés une seule fois (consume_recovery_code).
Présentez-les une fois à l'utilisateur à la génération ; ils ne sont pas réaffichables.
Anti-rejeu et rate-limit
Un code TOTP déjà utilisé est refusé (anti-rejeu) ; le challenge et la revalidation sont limités en tentatives et en fenêtre temporelle.
Ces protections sont actives par défaut.
13. Politique de stockage des secrets MFA
Ce qui est en place¶
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éfixeenc:) ; - 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_KEYpositionné dans l'environnement ;- accès à la table
auth_mfa_factorslimité à l'utilisateur applicatif ; - secrets jamais loggés (
totp_secretetrecovery_codesont dans les champs redactés desanitize_auth_audit_metadata()) ; - base de données non exposée publiquement.
Production¶
Le chiffrement Fernet est en place (SEC-MFA-SECRET-ENCRYPTION-001) et la rotation de clé l'est aussi (MFA-KEY-ROTATION-001).
Deux exigences restent à la charge de l'exploitant : la sauvegarde de la clé, dont la perte rend les secrets illisibles, et la revue de sécurité de son propre déploiement.
Protection additionnelle recommandée en production :
- restreindre les droits d'accès à la table
auth_mfa_factorsau strict minimum applicatif ; - stocker
FORGE_MFA_SECRET_KEYdans un gestionnaire de secrets (Vault, AWS Secrets Manager…) ; - appeler
validate_mfa_secret_key_config()au démarrage applicatif (cf. section 7) ; - chiffrement du disque de la base de données ;
- ne pas exporter
auth_mfa_factorsdans des dumps non chiffrés ; - documenter la procédure de rotation et de sauvegarde/restauration de la clé.
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 :
- 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¶
Ce qui relevait du paquet est livré. Ce qui reste relève de l'exploitant, et ne peut pas en relever autrement : Forge ne sait ni où vous sauvegardez vos clés, ni qui relit votre déploiement.
Chiffrement applicatif des secrets TOTP✓ livré (SEC-MFA-SECRET-ENCRYPTION-001) : Fernet +FORGE_MFA_SECRET_KEY.Politique de rotation✓ livré (MFA-KEY-ROTATION-001) :FORGE_MFA_SECRET_KEY_PREVIOUS,rotate_totp_secret,uses_current_key.Tests dédiés au stockage chiffré✓ livré (SEC-MFA-SECRET-ENCRYPTION-001) :tests/test_mfa_secret_crypto.py.- Sauvegarde de la clé de chiffrement : à votre charge. Sa perte rend tous les secrets TOTP illisibles, et aucun facteur ne se revalide.
- Revue de sécurité de votre déploiement : à votre charge.
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é |
14. Faire tourner la clé de chiffrement
Changer FORGE_MFA_SECRET_KEY sans précaution rend tous les secrets TOTP illisibles au même instant.
Chaque porteur d'un facteur perd alors son second facteur, et une mesure d'hygiène devient une panne d'authentification.
FORGE_MFA_SECRET_KEY_PREVIOUS déclare les clés retirées, séparées par des virgules.
Elles servent uniquement au déchiffrement, le chiffrement utilisant toujours la clé courante.
La procédure en quatre temps.
-
Générer la nouvelle clé, sans encore la poser.
-
Poser la nouvelle clé comme clé courante, et l'ancienne comme clé retirée.
À ce stade tout fonctionne, les anciens secrets restent lisibles et les nouveaux sont chiffrés avec la nouvelle clé.
-
Rechiffrer les secrets existants, au rythme voulu.
-
Retirer
FORGE_MFA_SECRET_KEY_PREVIOUSune fois qu'aucun secret ne dépend plus de l'ancienne clé.
Pourquoi Forge ne balaie pas la base lui-même
La table des facteurs appartient à l'application, et Forge n'en connaît ni le nom ni les colonnes.
Le paquet fournit la primitive de rechiffrement, l'application décide où elle s'applique.
C'est le principe 1, qui sépare le framework de l'application métier.
Le secret en clair ne transite jamais
rotate_totp_secret travaille de jeton chiffré à jeton chiffré, via MultiFernet.rotate.
Le secret TOTP n'est ni rendu à l'appelant ni journalisé pendant la rotation.
Une clé retirée reste un secret
Tant que FORGE_MFA_SECRET_KEY_PREVIOUS est renseignée, sa valeur déchiffre des secrets réels.
Elle se protège comme la clé courante, et se retire dès la fin du rechiffrement.
Par défaut, l'anti-rejeu vaut par processus, pas par application
Le registre des codes déjà utilisés vit en mémoire du processus.
Derrière un serveur à plusieurs workers, gunicorn typiquement, chaque worker a le sien.
Un même code TOTP peut donc être accepté une fois par worker, soit jusqu'à autant de fois qu'il y a de workers.
La fenêtre est courte, un code TOTP vivant trente secondes, et l'attaquant doit déjà détenir le code.
Le risque réel est donc le rejeu d'un code intercepté, pas la découverte d'un code.
Cette note invoquait auparavant le rate-limit du challenge en atténuation.
C'était trompeur, et corrigé ici : ce compteur vit dans la même mémoire de processus, et souffre exactement du même défaut.
Le paragraphe suivant le dit.
Trois remèdes, au choix de l'exploitant.
- Servir l'authentification par un seul worker, ce qui suffit à beaucoup d'applications.
- Installer le registre partagé livré par Forge, voir le paragraphe suivant.
- Porter le registre dans un magasin partagé de votre cru, en écrivant une classe conforme au protocole
TotpReplayStore.
Le registre n'est pas non plus persisté : un redémarrage l'oublie, avec la même fenêtre de moins de trente secondes.
Le rate-limit du challenge vaut lui aussi par processus
MFA_CHALLENGE_MAX_ATTEMPTS vaut cinq essais par fenêtre de cinq minutes, et MFA_REVALIDATION_MAX_ATTEMPTS trois.
Ces compteurs vivent dans core.auth.rate_limit, en mémoire du processus.
Derrière quatre travailleurs Gunicorn, ce que l'unité systemd engendrée par Forge lance, les cinq essais en deviennent jusqu'à vingt, et le verrouillage ne suit pas l'attaquant d'un travailleur à l'autre.
La parade est la même que pour la connexion, et elle se pose au proxy, qui compte pour tous les travailleurs.
La configuration Nginx engendrée par forge deploy:init porte cette limite sur /login depuis DEPLOY-NGINX-RATE-LIMIT-001.
Le challenge MFA vit sur une autre route, que Forge ne connaît pas : le paquet ne pose aucune route, c'est l'application qui les écrit.
Ajoutez donc un location de même forme sur votre route de challenge, sans quoi elle reste la seule porte non bornée du parcours d'authentification.
Un compteur applicatif partagé reste possible sans que Forge en livre un : check_auth_rate_limit accepte une liste de tentatives chargée d'où vous voulez.
Partager le registre entre tous les processus
Forge livre DbTotpReplayStore, adossé au backend BDD du projet, donc commun à tous les workers.
Il ne s'active pas tout seul, l'application le pose au démarrage en une ligne visible.
from forge_mvc_mfa import set_replay_store
from forge_mvc_mfa.replay_store_db import DbTotpReplayStore
set_replay_store(DbTotpReplayStore())
La table se provisionne comme celle de tout opt-in adossé à la base.
forge mfa:init # écrit la migration dans mvc/migrations/, sans l'exécuter
forge migration:apply # après relecture
Cette table n'est requise que si vous installez ce registre.
Un projet qui garde le défaut n'a aucune table à créer, forge-mvc-mfa restant une bibliothèque sans persistance.
Le coût est d'une écriture par validation de code.
En contrepartie la garantie devient exacte, y compris entre processus, et elle n'exige ni Redis ni aucune dépendance nouvelle.
Persistance applicative
Forge fournit les helpers et les contrats (AuthMfaFactor, codes) ; l'application choisit la persistance (table, schéma), cohérent avec ADR-008.
Indépendance du cœur
Le cœur de Forge ne dépend pas de forge-mvc-mfa : la dépendance va de l'opt-in vers le cœur.
15. Rendre le facteur obligatoire pour un rôle
Le paquet savait dire si un utilisateur a un facteur actif. Il ne savait pas dire s'il devrait en avoir un (MFA-REQUIRED-BY-ROLE-001).
L'application écrivait donc, dans chaque contrôleur sensible, un « si cet utilisateur est administrateur et n'a pas de MFA, alors refuser ». Elle l'écrivait bien la première fois, et l'oubliait au troisième écran d'administration ajouté six mois plus tard.
from forge_mvc_mfa.policy import check_mfa_requirement
verdict = check_mfa_requirement(session, facteurs_de_l_utilisateur)
if verdict.must_enroll:
return BaseController.redirect_with_flash(request, "/mfa/setup", verdict.reason)
La politique n'active rien
Rendre un facteur obligatoire ne peut pas le créer à la place de l'utilisateur : il faut son téléphone, et son consentement.
La politique dit qu'un accès doit être refusé tant que le facteur manque ; c'est à l'application de conduire l'utilisateur vers l'inscription, et reason lui donne le message.
Le paquet ne connaît pas forge-mvc-rbac
Aucun opt-in n'importe un autre.
Les rôles sont lus dans la session, où l'authentification les a rangés, et la politique n'a pas besoin de savoir d'où ils viennent. Trois emplacements sont acceptés, user.roles, user.role et roles à la racine : les applications les emploient tous les trois, et n'en reconnaître qu'un ferait échouer la politique en silence, ce qui est la pire issue pour un contrôle de sécurité.
check_mfa_requirement ne lève jamais
Un contrôle de sécurité qui échoue en levant sur une session mal formée priverait d'accès un utilisateur légitime.
Il rend un verdict, et l'appelant décide.
Sans MFA_REQUIRED_ROLES, rien n'est obligatoire : le paquet n'impose pas une politique que personne n'a demandée. Les noms de rôles sont normalisés en minuscules, une majuscule ne devant pas faire échouer une politique.
Les fonctions vivent dans policy.py (check_mfa_requirement, required_roles, roles_of, is_mfa_required_for, MfaRequirement).
Voir aussi¶
- Cœur MFA (mfa.py) : enrôlement, challenge, revalidation.
- Codes de récupération (recovery.py) : génération et consommation.
- Chiffrement des secrets (secret_crypto.py) : Fernet,
FORGE_MFA_SECRET_KEY. - Protection anti-rejeu (totp_replay.py).
- Welcome-MFA : parcours d'apprentissage.