Ajouter l'authentification multifacteur à votre application Web

Ce document explique comment ajouter l'authentification multifacteur par SMS à votre application Web.

L'authentification multifacteur renforce la sécurité de votre application. Bien que les pirates informatiques compromettent les mots de passe et les comptes de réseaux sociaux, l'interception des SMS est plus difficile.

Avant de commencer

  1. Activez au moins un fournisseur compatible avec l'authentification multifacteur. Tous les fournisseurs sont compatibles avec la MFA, sauf l'authentification par téléphone, l'authentification anonyme et l'Apple Game Center.

  2. Assurez-vous que votre application valide les adresses e-mail des utilisateurs. L'authentification multifacteur nécessite une validation de l'adresse e-mail. Cela empêche les utilisateurs malveillants de s'enregistrer à un service avec une adresse e-mail dont ils ne sont pas propriétaire, puis de bloquer le propriétaire réel en ajoutant un second facteur.

Utiliser l'architecture mutualisée

Si vous activez l'authentification multifacteur pour l'utiliser dans un environnement multilocataire, veillez à effectuer les étapes suivantes (en plus des autres instructions fournies dans ce document) :

  1. Dans la console Google Cloud, sélectionnez le locataire avec lequel vous souhaitez travailler.

  2. Dans votre code, définissez le champ tenantId de l'instance Auth sur l'ID de votre locataire. Exemple :

    Version Web 9

    import { getAuth } from "firebase/auth";
    
    const auth = getAuth(app);
    auth.tenantId = "myTenantId1";
    

    Version Web 8

    firebase.auth().tenantId = 'myTenantId1';
    

Activer l'authentification multifacteur

  1. Accédez à la page Identity Platform MFA dans la console Google Cloud.
    Accéder à la page MFA

  2. Dans la zone intitulée Authentification multifacteur par SMS, cliquez sur Activer.

  3. Saisissez les numéros de téléphone avec lesquels vous souhaitez tester votre application. Bien que cette étape soit facultative, il est fortement recommandé d'enregistrer des numéros de téléphone de test pour éviter toute limitation lors du développement.

  4. Si vous n'avez pas encore autorisé le domaine de votre application, ajoutez-le à la liste des autorisations en cliquant sur Ajouter un domaine à droite.

  5. Cliquez sur Enregistrer.

Choisir un modèle d'inscription

Vous pouvez décider si votre application nécessite une authentification multifacteur, et comment et quand inscrire vos utilisateurs. Voici quelques modèles courants :

  • Inscrivez le deuxième facteur de l'utilisateur dans le cadre de l'inscription. Utilisez cette méthode si votre application nécessite une authentification multifacteur pour tous les utilisateurs.

  • Proposez une option désactivable pour inscrire un second facteur lors de l'enregistrement. Les applications qui souhaitent encourager mais pas exiger l'authentification multifacteur peuvent préférer cette approche.

  • Offrez la possibilité d'ajouter un second facteur à partir de la page de gestion de compte ou de profil de l'utilisateur, au lieu de l'écran d'inscription. Cela minimise les frictions lors du processus d'enregistrement tout en rendant encore l'authentification multifacteur disponible pour les utilisateurs sensibles à la sécurité.

  • Exigez l'ajout incrémentiel d'un second facteur lorsque l'utilisateur souhaite accéder aux fonctionnalités présentant des exigences de sécurité accrues.

Configurer l'outil de vérification reCAPTCHA

Avant de pouvoir envoyer des codes SMS, vous devez configurer un outil de validation reCAPTCHA. Identity Platform utilise reCAPTCHA pour éviter les abus en garantissant que les demandes de validation de numéro de téléphone proviennent de l'un des domaines autorisés de votre application.

Vous n'avez pas besoin de configurer manuellement un client reCAPTCHA. L'objet RecaptchaVerifier du SDK client crée et initialise automatiquement les clés et les secrets client nécessaires.

Utiliser la méthode reCAPTCHA invisible

L'objet RecaptchaVerifier est compatible avec la méthode reCAPTCHA invisible, qui permet souvent de valider l'utilisateur sans aucune interaction. Pour utiliser la méthode reCAPTCHA visible, créez un RecaptchaVerifier avec le paramètre size défini sur invisible, et spécifiez l'ID de l'élément d'interface utilisateur qui lance l'inscription multifacteur :

Version Web 9

import { RecaptchaVerifier } from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier("sign-in-button", {
    "size": "invisible",
    "callback": function(response) {
        // reCAPTCHA solved, you can proceed with
        // phoneAuthProvider.verifyPhoneNumber(...).
        onSolvedRecaptcha();
    }
}, auth);

Version Web 8

var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('sign-in-button', {
'size': 'invisible',
'callback': function(response) {
  // reCAPTCHA solved, you can proceed with phoneAuthProvider.verifyPhoneNumber(...).
  onSolvedRecaptcha();
}
});

Utiliser le widget reCAPTCHA

Pour utiliser un widget reCAPTCHA visible, créez un élément HTML qui contiendra le widget, puis créez un objet RecaptchaVerifier avec l'ID du conteneur d'interface utilisateur. Vous pouvez également définir des rappels appelés lorsque le test reCAPTCHA est résolu ou expire :

Version Web 9

import { RecaptchaVerifier } from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier(
    "recaptcha-container",

    // Optional reCAPTCHA parameters.
    {
      "size": "normal",
      "callback": function(response) {
        // reCAPTCHA solved, you can proceed with
        // phoneAuthProvider.verifyPhoneNumber(...).
        onSolvedRecaptcha();
      },
      "expired-callback": function() {
        // Response expired. Ask user to solve reCAPTCHA again.
        // ...
      }
    }, auth
);

Version Web 8

var recaptchaVerifier = new firebase.auth.RecaptchaVerifier(
  'recaptcha-container',
  // Optional reCAPTCHA parameters.
  {
    'size': 'normal',
    'callback': function(response) {
      // reCAPTCHA solved, you can proceed with phoneAuthProvider.verifyPhoneNumber(...).
      // ...
      onSolvedRecaptcha();
    },
    'expired-callback': function() {
      // Response expired. Ask user to solve reCAPTCHA again.
      // ...
    }
  });

Préafficher la méthode reCAPTCHA

Si vous le souhaitez, vous pouvez préafficher la méthode reCAPTCHA avant de lancer l'inscription à deux facteurs :

Version Web 9

recaptchaVerifier.render()
    .then(function (widgetId) {
        window.recaptchaWidgetId = widgetId;
    });

Version Web 8

recaptchaVerifier.render()
  .then(function(widgetId) {
    window.recaptchaWidgetId = widgetId;
  });

Une fois l'opération render() résolue, vous obtenez l'ID de widget de la méthode reCAPTCHA, que vous pouvez utiliser pour appeler l'API reCAPTCHA :

var recaptchaResponse = grecaptcha.getResponse(window.recaptchaWidgetId);

RecaptchaVerifier extrait cette logique avec la méthode verify. Vous n'avez donc pas besoin de gérer la variable grecaptcha directement.

Inscrire un second facteur

Pour inscrire un nouveau facteur secondaire pour un utilisateur, procédez comme suit :

  1. Réauthentifiez l'utilisateur.

  2. Demandez à l'utilisateur de saisir son numéro de téléphone.

  3. Initialisez le vérificateur reCAPTCHA comme illustré à la section précédente. Ignorez cette étape si une instance RecaptchaValidator est déjà configurée :

    Version Web 9

    import { RecaptchaVerifier } from "firebase/auth";
    
    const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
    

    Version Web 8

    var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
    
  4. Obtenez une session multifacteur pour l'utilisateur :

    Version Web 9

    import { multiFactor } from "firebase/auth";
    
    multiFactor(user).getSession().then(function (multiFactorSession) {
        // ...
    });
    

    Version Web 8

    user.multiFactor.getSession().then(function(multiFactorSession) {
      // ...
    })
    
  5. Initialisez un objet PhoneInfoOptions avec le numéro de téléphone de l'utilisateur et la session multifacteur :

    Version Web 9

    // Specify the phone number and pass the MFA session.
    const phoneInfoOptions = {
      phoneNumber: phoneNumber,
      session: multiFactorSession
    };
    

    Version Web 8

    // Specify the phone number and pass the MFA session.
    var phoneInfoOptions = {
      phoneNumber: phoneNumber,
      session: multiFactorSession
    };
    
  6. Envoyez un message de validation sur le téléphone de l'utilisateur :

    Version Web 9

    import { PhoneAuthProvider } from "firebase/auth";
    
    const phoneAuthProvider = new PhoneAuthProvider(auth);
    phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
        .then(function (verificationId) {
            // verificationId will be needed to complete enrollment.
        });
    

    Version Web 8

    var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
    // Send SMS verification code.
    return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
      .then(function(verificationId) {
        // verificationId will be needed for enrollment completion.
      })
    

    Bien que cela ne soit pas obligatoire, il est recommandé d'informer préalablement les utilisateurs qu'ils recevront un SMS, et que les tarifs standards s'appliquent.

  7. Si la requête échoue, réinitialisez la méthode reCAPTCHA, puis répétez l'étape précédente afin que l'utilisateur puisse réessayer. Notez que verifyPhoneNumber() réinitialise automatiquement la méthode reCAPTCHA lorsqu'il génère une erreur, car les jetons reCAPTCHA ne sont utilisés qu'une seule fois.

    Version Web 9

    recaptchaVerifier.clear();
    

    Version Web 8

    recaptchaVerifier.clear();
    
  8. Une fois le code SMS envoyé, demandez à l'utilisateur de le vérifier :

    Version Web 9

    // Ask user for the verification code. Then:
    const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
    

    Version Web 8

    // Ask user for the verification code. Then:
    var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
    
  9. Initialisez un objet MultiFactorAssertion à l'aide du PhoneAuthCredential :

    Version Web 9

    import { PhoneMultiFactorGenerator } from "firebase/auth";
    
    const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
    

    Version Web 8

    var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
    
  10. Terminez l'inscription. Vous pouvez éventuellement spécifier un nom à afficher pour le deuxième facteur. Cela s'avère utile pour les utilisateurs qui ont plusieurs facteurs, car le numéro de téléphone est masqué pendant le processus d'authentification (par exemple, +1******1234).

    Version Web 9

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    multiFactor(user).enroll(multiFactorAssertion, "My personal phone number");
    

    Version Web 8

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    user.multiFactor.enroll(multiFactorAssertion, 'My personal phone number');
    

Le code ci-dessous montre un exemple complet d'inscription d'un second facteur :

Version Web 9

import {
    multiFactor, PhoneAuthProvider, PhoneMultiFactorGenerator,
    RecaptchaVerifier
} from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
multiFactor(user).getSession()
    .then(function (multiFactorSession) {
        // Specify the phone number and pass the MFA session.
        const phoneInfoOptions = {
            phoneNumber: phoneNumber,
            session: multiFactorSession
        };

        const phoneAuthProvider = new PhoneAuthProvider(auth);

        // Send SMS verification code.
        return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier);
    }).then(function (verificationId) {
        // Ask user for the verification code. Then:
        const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
        const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);

        // Complete enrollment.
        return multiFactor(user).enroll(multiFactorAssertion, mfaDisplayName);
    });

Version Web 8

var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
user.multiFactor.getSession().then(function(multiFactorSession) {
  // Specify the phone number and pass the MFA session.
  var phoneInfoOptions = {
    phoneNumber: phoneNumber,
    session: multiFactorSession
  };
  var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
  // Send SMS verification code.
  return phoneAuthProvider.verifyPhoneNumber(
      phoneInfoOptions, recaptchaVerifier);
})
.then(function(verificationId) {
  // Ask user for the verification code.
  var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
  var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
  // Complete enrollment.
  return user.multiFactor.enroll(multiFactorAssertion, mfaDisplayName);
});

Félicitations ! Vous avez enregistré un second facteur d'authentification pour un utilisateur.

Connecter des utilisateurs avec un second facteur

Pour connecter un utilisateur via la validation SMS à deux facteurs, procédez comme suit :

  1. Connectez l'utilisateur avec son premier facteur, puis détectez l'erreur auth/multi-factor-auth-required. Cette erreur contient un résolveur, des indices sur les seconds facteurs inscrits et une session sous-jacente attestant que l'utilisateur s'est authentifié avec succès avec le premier facteur.

    Par exemple, si le premier facteur de l'utilisateur est une adresse e-mail et un mot de passe :

    Version Web 9

    import { getAuth, getMultiFactorResolver} from "firebase/auth";
    
    const auth = getAuth();
    signInWithEmailAndPassword(auth, email, password)
        .then(function (userCredential) {
            // User successfully signed in and is not enrolled with a second factor.
        })
        .catch(function (error) {
            if (error.code == 'auth/multi-factor-auth-required') {
                // The user is a multi-factor user. Second factor challenge is required.
                resolver = getMultiFactorResolver(auth, error);
                // ...
            } else if (error.code == 'auth/wrong-password') {
                // Handle other errors such as wrong password.
            }
    });
    

    Version Web 8

    firebase.auth().signInWithEmailAndPassword(email, password)
      .then(function(userCredential) {
        // User successfully signed in and is not enrolled with a second factor.
      })
      .catch(function(error) {
        if (error.code == 'auth/multi-factor-auth-required') {
          // The user is a multi-factor user. Second factor challenge is required.
          resolver = error.resolver;
          // ...
        } else if (error.code == 'auth/wrong-password') {
          // Handle other errors such as wrong password.
        } ...
      });
    

    Si le premier facteur de l'utilisateur est un fournisseur fédéré, tel que OAuth, SAML ou OIDC, détectez l'erreur après avoir appelé signInWithPopup() ou signInWithRedirect().

  2. Si l'utilisateur possède plusieurs facteurs secondaires inscrits, demandez-lui lequel utiliser :

    Version Web 9

    // Ask user which second factor to use.
    // You can get the masked phone number via resolver.hints[selectedIndex].phoneNumber
    // You can get the display name via resolver.hints[selectedIndex].displayName
    
    if (resolver.hints[selectedIndex].factorId ===
        PhoneMultiFactorGenerator.FACTOR_ID) {
        // User selected a phone second factor.
        // ...
    } else if (resolver.hints[selectedIndex].factorId ===
               TotpMultiFactorGenerator.FACTOR_ID) {
        // User selected a TOTP second factor.
        // ...
    } else {
        // Unsupported second factor.
    }
    

    Version Web 8

    // Ask user which second factor to use.
    // You can get the masked phone number via resolver.hints[selectedIndex].phoneNumber
    // You can get the display name via resolver.hints[selectedIndex].displayName
    if (resolver.hints[selectedIndex].factorId === firebase.auth.PhoneMultiFactorGenerator.FACTOR_ID) {
      // User selected a phone second factor.
      // ...
    } else if (resolver.hints[selectedIndex].factorId === firebase.auth.TotpMultiFactorGenerator.FACTOR_ID) {
      // User selected a TOTP second factor.
      // ...
    } else {
      // Unsupported second factor.
    }
    
  3. Initialisez le vérificateur reCAPTCHA comme illustré à la section précédente. Ignorez cette étape si une instance RecaptchaValidator est déjà configurée :

    Version Web 9

    import { RecaptchaVerifier } from "firebase/auth";
    
    recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
    

    Version Web 8

    var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
    
  4. Initialisez un objet PhoneInfoOptions avec le numéro de téléphone de l'utilisateur et la session multifacteur. Ces valeurs sont contenues dans l'objet resolver transmis à l'erreur auth/multi-factor-auth-required :

    Version Web 9

    const phoneInfoOptions = {
        multiFactorHint: resolver.hints[selectedIndex],
        session: resolver.session
    };
    

    Version Web 8

    var phoneInfoOptions = {
      multiFactorHint: resolver.hints[selectedIndex],
      session: resolver.session
    };
    
  5. Envoyez un message de validation sur le téléphone de l'utilisateur :

    Version Web 9

    // Send SMS verification code.
    const phoneAuthProvider = new PhoneAuthProvider(auth);
    phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
        .then(function (verificationId) {
            // verificationId will be needed for sign-in completion.
        });
    

    Version Web 8

    var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
    // Send SMS verification code.
    return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
      .then(function(verificationId) {
        // verificationId will be needed for sign-in completion.
      })
    
  6. Si la requête échoue, réinitialisez la méthode reCAPTCHA, puis répétez l'étape précédente afin que l'utilisateur puisse réessayer :

    Version Web 9

    recaptchaVerifier.clear();
    

    Version Web 8

    recaptchaVerifier.clear();
    
  7. Une fois le code SMS envoyé, demandez à l'utilisateur de le vérifier :

    Version Web 9

    const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
    

    Version Web 8

    // Ask user for the verification code. Then:
    var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
    
  8. Initialisez un objet MultiFactorAssertion à l'aide du PhoneAuthCredential :

    Version Web 9

    const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
    

    Version Web 8

    var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
    
  9. Appelez resolver.resolveSignIn() pour terminer l'authentification secondaire. Vous pouvez ensuite accéder au résultat de connexion d'origine, qui inclut les données spécifiques au fournisseur et les identifiants d'authentification :

    Version Web 9

    // Complete sign-in. This will also trigger the Auth state listeners.
    resolver.resolveSignIn(multiFactorAssertion)
        .then(function (userCredential) {
            // userCredential will also contain the user, additionalUserInfo, optional
            // credential (null for email/password) associated with the first factor sign-in.
    
            // For example, if the user signed in with Google as a first factor,
            // userCredential.additionalUserInfo will contain data related to Google
            // provider that the user signed in with.
            // - user.credential contains the Google OAuth credential.
            // - user.credential.accessToken contains the Google OAuth access token.
            // - user.credential.idToken contains the Google OAuth ID token.
        });
    

    Version Web 8

    // Complete sign-in. This will also trigger the Auth state listeners.
    resolver.resolveSignIn(multiFactorAssertion)
      .then(function(userCredential) {
        // userCredential will also contain the user, additionalUserInfo, optional
        // credential (null for email/password) associated with the first factor sign-in.
        // For example, if the user signed in with Google as a first factor,
        // userCredential.additionalUserInfo will contain data related to Google provider that
        // the user signed in with.
        // user.credential contains the Google OAuth credential.
        // user.credential.accessToken contains the Google OAuth access token.
        // user.credential.idToken contains the Google OAuth ID token.
      });
    

Le code ci-dessous illustre un exemple complet de connexion d'un utilisateur multifacteur :

Version Web 9

import {
    getAuth,
    getMultiFactorResolver,
    PhoneAuthProvider,
    PhoneMultiFactorGenerator,
    RecaptchaVerifier,
    signInWithEmailAndPassword
} from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);

const auth = getAuth();
signInWithEmailAndPassword(auth, email, password)
    .then(function (userCredential) {
        // User is not enrolled with a second factor and is successfully
        // signed in.
        // ...
    })
    .catch(function (error) {
        if (error.code == 'auth/multi-factor-auth-required') {
            const resolver = getMultiFactorResolver(auth, error);
            // Ask user which second factor to use.
            if (resolver.hints[selectedIndex].factorId ===
                PhoneMultiFactorGenerator.FACTOR_ID) {
                const phoneInfoOptions = {
                    multiFactorHint: resolver.hints[selectedIndex],
                    session: resolver.session
                };
                const phoneAuthProvider = new PhoneAuthProvider(auth);
                // Send SMS verification code
                return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
                    .then(function (verificationId) {
                        // Ask user for the SMS verification code. Then:
                        const cred = PhoneAuthProvider.credential(
                            verificationId, verificationCode);
                        const multiFactorAssertion =
                            PhoneMultiFactorGenerator.assertion(cred);
                        // Complete sign-in.
                        return resolver.resolveSignIn(multiFactorAssertion)
                    })
                    .then(function (userCredential) {
                        // User successfully signed in with the second factor phone number.
                    });
            } else if (resolver.hints[selectedIndex].factorId ===
                       TotpMultiFactorGenerator.FACTOR_ID) {
                // Handle TOTP MFA.
                // ...
            } else {
                // Unsupported second factor.
            }
        } else if (error.code == 'auth/wrong-password') {
            // Handle other errors such as wrong password.
        }
    });

Version Web 8

var resolver;
firebase.auth().signInWithEmailAndPassword(email, password)
  .then(function(userCredential) {
    // User is not enrolled with a second factor and is successfully signed in.
    // ...
  })
  .catch(function(error) {
    if (error.code == 'auth/multi-factor-auth-required') {
      resolver = error.resolver;
      // Ask user which second factor to use.
      if (resolver.hints[selectedIndex].factorId ===
          firebase.auth.PhoneMultiFactorGenerator.FACTOR_ID) {
        var phoneInfoOptions = {
          multiFactorHint: resolver.hints[selectedIndex],
          session: resolver.session
        };
        var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
        // Send SMS verification code
        return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
          .then(function(verificationId) {
            // Ask user for the SMS verification code.
            var cred = firebase.auth.PhoneAuthProvider.credential(
                verificationId, verificationCode);
            var multiFactorAssertion =
                firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
            // Complete sign-in.
            return resolver.resolveSignIn(multiFactorAssertion)
          })
          .then(function(userCredential) {
            // User successfully signed in with the second factor phone number.
          });
      } else if (resolver.hints[selectedIndex].factorId ===
        firebase.auth.TotpMultiFactorGenerator.FACTOR_ID) {
        // Handle TOTP MFA.
        // ...
      } else {
        // Unsupported second factor.
      }
    } else if (error.code == 'auth/wrong-password') {
      // Handle other errors such as wrong password.
    } ...
  });

Félicitations ! Vous avez connecté un utilisateur à l'aide de l'authentification multifacteur.

Étapes suivantes