two-factor-authentication-best-practices

Configurez des applications d'authentification TOTP, envoyez des codes OTP par e-mail/SMS, gérez les codes de secours, gérez les appareils de confiance et implémentez des flux de connexion 2FA à l'aide du plugin `twoFactor` de Better Auth. À utiliser lorsque les utilisateurs ont besoin de MFA, d'authentification multi-facteurs, de la configuration d'un authentificateur ou de la sécurisation des connexions avec Better Auth.

npx skills add https://github.com/better-auth/skills --skill two-factor-authentication-best-practices

Configuration

  1. Ajouter le plugin twoFactor() à la configuration serveur avec issuer
  2. Ajouter le plugin twoFactorClient() à la configuration client
  3. Exécuter npx @better-auth/cli migrate
  4. Vérifier : vérifier que la colonne twoFactorSecret existe sur la table user
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";

export const auth = betterAuth({
  appName: "My App",
  plugins: [
    twoFactor({
      issuer: "My App",
    }),
  ],
});

Configuration côté client

import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [
    twoFactorClient({
      onTwoFactorRedirect() {
        window.location.href = "/2fa";
      },
    }),
  ],
});

Activation de l'authentification 2FA pour les utilisateurs

Requiert une vérification de mot de passe. Retourne l'URI TOTP (pour un code QR) et les codes de secours.

const enable2FA = async (password: string) => {
  const { data, error } = await authClient.twoFactor.enable({
    password,
  });

  if (data) {
    // data.totpURI — générer un code QR à partir de ceci
    // data.backupCodes — afficher à l'utilisateur
  }
};

twoFactorEnabled n'est défini sur true que lorsque la première vérification TOTP réussit. À remplacer par skipVerificationOnEnable: true (non recommandé).

TOTP (Application d'authentification)

Affichage du code QR

import QRCode from "react-qr-code";

const TotpSetup = ({ totpURI }: { totpURI: string }) => {
  return <QRCode value={totpURI} />;
};

Vérification des codes TOTP

Accepte les codes d'une période avant/après l'heure actuelle :

const verifyTotp = async (code: string) => {
  const { data, error } = await authClient.twoFactor.verifyTotp({
    code,
    trustDevice: true,
  });
};

Options de configuration TOTP

twoFactor({
  totpOptions: {
    digits: 6, // 6 ou 8 chiffres (défaut : 6)
    period: 30, // Période de validité du code en secondes (défaut : 30)
  },
});

OTP (Email/SMS)

Configuration de la livraison OTP

import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  plugins: [
    twoFactor({
      otpOptions: {
        sendOTP: async ({ user, otp }, ctx) => {
          await sendEmail({
            to: user.email,
            subject: "Your verification code",
            text: `Your code is: ${otp}`,
          });
        },
        period: 5, // Validité du code en minutes (défaut : 3)
        digits: 6, // Nombre de chiffres (défaut : 6)
        allowedAttempts: 5, // Nombre maximum de tentatives de vérification (défaut : 5)
      },
    }),
  ],
});

Envoi et vérification OTP

Envoyer : authClient.twoFactor.sendOtp(). Vérifier : authClient.twoFactor.verifyOtp({ code, trustDevice: true }).

Sécurité du stockage OTP

Configurer comment les codes OTP sont stockés dans la base de données :

twoFactor({
  otpOptions: {
    storeOTP: "encrypted", // Options : "plain", "encrypted", "hashed"
  },
});

Pour un chiffrement personnalisé :

twoFactor({
  otpOptions: {
    storeOTP: {
      encrypt: async (token) => myEncrypt(token),
      decrypt: async (token) => myDecrypt(token),
    },
  },
});

Codes de secours

Générés automatiquement lors de l'activation de l'authentification 2FA. Chaque code ne peut être utilisé qu'une seule fois.

Affichage des codes de secours

const BackupCodes = ({ codes }: { codes: string[] }) => {
  return (
    <div>
      <p>Save these codes in a secure location:</p>
      <ul>
        {codes.map((code, i) => (
          <li key={i}>{code}</li>
        ))}
      </ul>
    </div>
  );
};

Regénération des codes de secours

Invalide tous les codes précédents :

const regenerateBackupCodes = async (password: string) => {
  const { data, error } = await authClient.twoFactor.generateBackupCodes({
    password,
  });
  // data.backupCodes contient les nouveaux codes
};

Utilisation des codes de secours pour la récupération

const verifyBackupCode = async (code: string) => {
  const { data, error } = await authClient.twoFactor.verifyBackupCode({
    code,
    trustDevice: true,
  });
};

Configuration des codes de secours

twoFactor({
  backupCodeOptions: {
    amount: 10, // Nombre de codes à générer (défaut : 10)
    length: 10, // Longueur de chaque code (défaut : 10)
    storeBackupCodes: "encrypted", // Options : "plain", "encrypted"
  },
});

Gestion de l'authentification 2FA lors de la connexion

La réponse inclut twoFactorRedirect: true quand l'authentification 2FA est requise :

Flux de connexion

  1. Appeler signIn.email({ email, password })
  2. Vérifier context.data.twoFactorRedirect dans onSuccess
  3. Si true, rediriger vers la page de vérification /2fa
  4. Vérifier via TOTP, OTP ou code de secours
  5. Un cookie de session est créé lors de la vérification réussie
const signIn = async (email: string, password: string) => {
  const { data, error } = await authClient.signIn.email(
    { email, password },
    {
      onSuccess(context) {
        if (context.data.twoFactorRedirect) {
          window.location.href = "/2fa";
        }
      },
    }
  );
};

Côté serveur : vérifier "twoFactorRedirect" in response lors de l'utilisation de auth.api.signInEmail.

Appareils approuvés

Passer trustDevice: true lors de la vérification. Durée de confiance par défaut : 30 jours (trustDeviceMaxAge). Se réinitialise à chaque connexion.

Considérations de sécurité

Gestion des sessions

Flux : identifiants → session supprimée → cookie 2FA temporaire (10 min par défaut) → vérification → session créée.

twoFactor({
  twoFactorCookieMaxAge: 600, // 10 minutes en secondes (défaut)
});

Limitation du débit

Intégrée : 3 requêtes par 10 secondes pour tous les endpoints 2FA. OTP dispose d'une limitation supplémentaire sur les tentatives :

twoFactor({
  otpOptions: {
    allowedAttempts: 5, // Nombre maximum de tentatives par code OTP (défaut : 5)
  },
});

Chiffrement au repos

Secrets TOTP : chiffrés avec le secret d'authentification. Codes de secours : chiffrés par défaut. OTP : configurable ("plain", "encrypted", "hashed"). Utilise une comparaison en temps constant pour la vérification.

L'authentification 2FA ne peut être activée que pour les comptes avec identifiants (email/mot de passe).

Désactivation de l'authentification 2FA

Requiert une confirmation de mot de passe. Révoque les enregistrements d'appareils approuvés :

const disable2FA = async (password: string) => {
  const { data, error } = await authClient.twoFactor.disable({
    password,
  });
};

Exemple de configuration complète

import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  appName: "My App",
  plugins: [
    twoFactor({
      // Paramètres TOTP
      issuer: "My App",
      totpOptions: {
        digits: 6,
        period: 30,
      },
      // Paramètres OTP
      otpOptions: {
        sendOTP: async ({ user, otp }) => {
          await sendEmail({
            to: user.email,
            subject: "Your verification code",
            text: `Your code is: ${otp}`,
          });
        },
        period: 5,
        allowedAttempts: 5,
        storeOTP: "encrypted",
      },
      // Paramètres des codes de secours
      backupCodeOptions: {
        amount: 10,
        length: 10,
        storeBackupCodes: "encrypted",
      },
      // Paramètres de session
      twoFactorCookieMaxAge: 600, // 10 minutes
      trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30 jours
    }),
  ],
});

Skills similaires