Agregue autenticación multifactor a su aplicación web

Si actualizó a Firebase Authentication con Identity Platform, puede agregar autenticación multifactor por SMS a su aplicación web.

La autenticación multifactor aumenta la seguridad de su aplicación. Si bien los atacantes suelen comprometer contraseñas y cuentas sociales, interceptar un mensaje de texto es más difícil.

Antes de que empieces

  1. Habilite al menos un proveedor que admita la autenticación multifactor. Todos los proveedores admiten MFA, excepto la autenticación telefónica, la autenticación anónima y Apple Game Center.

  2. Asegúrese de que su aplicación verifique los correos electrónicos de los usuarios. MFA requiere verificación por correo electrónico. Esto evita que actores maliciosos se registren en un servicio con un correo electrónico que no les pertenece y luego bloqueen al propietario real agregando un segundo factor.

Uso de multiinquilino

Si habilita la autenticación multifactor para su uso en un entorno multiinquilino , asegúrese de completar los siguientes pasos (además del resto de las instrucciones de este documento):

  1. En GCP Console, seleccione el inquilino con el que desea trabajar.

  2. En su código, establezca el campo tenantId en la instancia Auth en el ID de su inquilino. Por ejemplo:

    API modular web

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

    API con espacio de nombres web

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

Habilitar la autenticación multifactor

  1. Abra la página Autenticación > Método de inicio de sesión de Firebase console.

  2. En la sección Avanzado , habilite la autenticación multifactor por SMS .

    También debes ingresar los números de teléfono con los que probarás tu aplicación. Si bien es opcional, se recomienda encarecidamente registrar números de teléfono de prueba para evitar limitaciones durante el desarrollo.

  3. Si aún no ha autorizado el dominio de su aplicación, agréguelo a la lista de permitidos en la página Autenticación > Configuración de Firebase console.

Elegir un patrón de inscripción

Puede elegir si su aplicación requiere autenticación multifactor y cómo y cuándo inscribir a sus usuarios. Algunos patrones comunes incluyen:

  • Inscriba el segundo factor del usuario como parte del registro. Utilice este método si su aplicación requiere autenticación multifactor para todos los usuarios.

  • Ofrezca una opción que se pueda omitir para inscribir un segundo factor durante el registro. Las aplicaciones que quieran fomentar, pero no exigir, la autenticación multifactor pueden preferir este enfoque.

  • Brinde la posibilidad de agregar un segundo factor desde la cuenta del usuario o la página de administración de perfil, en lugar de la pantalla de registro. Esto minimiza la fricción durante el proceso de registro y al mismo tiempo hace que la autenticación multifactor esté disponible para los usuarios sensibles a la seguridad.

  • Requerir agregar un segundo factor de manera incremental cuando el usuario desee acceder a funciones con mayores requisitos de seguridad.

Configurar el verificador reCAPTCHA

Antes de poder enviar códigos SMS, debe configurar un verificador reCAPTCHA. Firebase usa reCAPTCHA para evitar abusos al garantizar que las solicitudes de verificación de números de teléfono provengan de uno de los dominios permitidos de su aplicación.

No es necesario configurar manualmente un cliente reCAPTCHA; El objeto RecaptchaVerifier del SDK del cliente crea e inicializa automáticamente las claves y secretos del cliente necesarios.

Usando reCAPTCHA invisible

El objeto RecaptchaVerifier admite reCAPTCHA invisible , que a menudo puede verificar al usuario sin requerir ninguna interacción. Para usar un reCAPTCHA invisible, cree un RecaptchaVerifier con el parámetro size establecido en invisible y especifique el ID del elemento de la interfaz de usuario que inicia la inscripción multifactor:

API modular web

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);

API con espacio de nombres web

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

Usando el widget reCAPTCHA

Para usar un widget reCAPTCHA visible, cree un elemento HTML para contener el widget, luego cree un objeto RecaptchaVerifier con el ID del contenedor de la interfaz de usuario. Opcionalmente, también puede configurar devoluciones de llamada que se invocan cuando el reCAPTCHA se resuelve o caduca:

API modular web

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
);

API con espacio de nombres web

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.
      // ...
    }
  });

Pre-renderizado del reCAPTCHA

Opcionalmente, puedes prerenderizar el reCAPTCHA antes de iniciar la inscripción de dos factores:

API modular web

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

API con espacio de nombres web

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

Después de que se resuelve render() , obtienes el ID del widget de reCAPTCHA, que puedes usar para realizar llamadas a la API de reCAPTCHA :

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

RecaptchaVerifier abstrae esta lógica con el método de verificación , por lo que no es necesario manejar la variable grecaptcha directamente.

Inscribir un segundo factor

Para inscribir un nuevo factor secundario para un usuario:

  1. Vuelva a autenticar al usuario.

  2. Solicite al usuario que ingrese su número de teléfono.

  3. Inicialice el verificador reCAPTCHA como se ilustra en la sección anterior. Omita este paso si ya hay una instancia de RecaptchaVerifier configurada:

    API modular web

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

    API con espacio de nombres web

    var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
    
  4. Obtenga una sesión multifactor para el usuario:

    API modular web

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

    API con espacio de nombres web

    user.multiFactor.getSession().then(function(multiFactorSession) {
      // ...
    })
    
  5. Inicialice un objeto PhoneInfoOptions con el número de teléfono del usuario y la sesión multifactor:

    API modular web

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

    API con espacio de nombres web

    // Specify the phone number and pass the MFA session.
    var phoneInfoOptions = {
      phoneNumber: phoneNumber,
      session: multiFactorSession
    };
    
  6. Enviar un mensaje de verificación al teléfono del usuario:

    API modular web

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

    API con espacio de nombres web

    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.
      })
    

    Si bien no es obligatorio, es una buena práctica informar a los usuarios de antemano que recibirán un mensaje SMS y que se aplican tarifas estándar.

  7. Si la solicitud falla, restablezca el reCAPTCHA y luego repita el paso anterior para que el usuario pueda intentarlo nuevamente. Tenga en cuenta que verifyPhoneNumber() restablecerá automáticamente el reCAPTCHA cuando arroje un error, ya que los tokens reCAPTCHA son de un solo uso.

    API modular web

    recaptchaVerifier.clear();
    

    API con espacio de nombres web

    recaptchaVerifier.clear();
    
  8. Una vez enviado el código SMS, solicite al usuario que verifique el código:

    API modular web

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

    API con espacio de nombres web

    // Ask user for the verification code. Then:
    var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
    
  9. Inicialice un objeto MultiFactorAssertion con PhoneAuthCredential :

    API modular web

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

    API con espacio de nombres web

    var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
    
  10. Completa la inscripción. Opcionalmente, puede especificar un nombre para mostrar para el segundo factor. Esto es útil para usuarios con múltiples segundos factores, ya que el número de teléfono está enmascarado durante el flujo de autenticación (por ejemplo, +1******1234).

    API modular web

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

    API con espacio de nombres web

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

El siguiente código muestra un ejemplo completo de cómo registrar un segundo factor:

API modular web

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);
    });

API con espacio de nombres web

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);
});

¡Felicidades! Registró exitosamente un segundo factor de autenticación para un usuario.

Iniciar sesión de usuarios con un segundo factor

Para iniciar sesión como usuario con verificación por SMS de dos factores:

  1. Inicie sesión como usuario con su primer factor y luego detecte el error auth/multi-factor-auth-required . Este error contiene un solucionador, sugerencias sobre los segundos factores inscritos y una sesión subyacente que demuestra que el usuario se autenticó exitosamente con el primer factor.

    Por ejemplo, si el primer factor del usuario fue un correo electrónico y una contraseña:

    API modular web

    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.
            }
    });
    

    API con espacio de nombres web

    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 el primer factor del usuario es un proveedor federado, como OAuth, SAML u OIDC, detecte el error después de llamar signInWithPopup() o signInWithRedirect() .

  2. Si el usuario tiene varios factores secundarios inscritos, pregúntale cuál usar:

    API modular web

    // 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.
    }
    

    API con espacio de nombres web

    // 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. Inicialice el verificador reCAPTCHA como se ilustra en la sección anterior. Omita este paso si ya hay una instancia de RecaptchaVerifier configurada:

    API modular web

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

    API con espacio de nombres web

    var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
    
  4. Inicialice un objeto PhoneInfoOptions con el número de teléfono del usuario y la sesión multifactor. Estos valores están contenidos en el objeto resolver pasado al error auth/multi-factor-auth-required :

    API modular web

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

    API con espacio de nombres web

    var phoneInfoOptions = {
      multiFactorHint: resolver.hints[selectedIndex],
      session: resolver.session
    };
    
  5. Enviar un mensaje de verificación al teléfono del usuario:

    API modular web

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

    API con espacio de nombres web

    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 solicitud falla, restablezca el reCAPTCHA y luego repita el paso anterior para que el usuario pueda intentarlo nuevamente:

    API modular web

    recaptchaVerifier.clear();
    

    API con espacio de nombres web

    recaptchaVerifier.clear();
    
  7. Una vez enviado el código SMS, solicite al usuario que verifique el código:

    API modular web

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

    API con espacio de nombres web

    // Ask user for the verification code. Then:
    var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
    
  8. Inicialice un objeto MultiFactorAssertion con PhoneAuthCredential :

    API modular web

    const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
    

    API con espacio de nombres web

    var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
    
  9. Llame a resolver.resolveSignIn() para completar la autenticación secundaria. Luego podrá acceder al resultado del inicio de sesión original, que incluye los datos estándar específicos del proveedor y las credenciales de autenticación:

    API modular web

    // 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.
        });
    

    API con espacio de nombres web

    // 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.
      });
    

El siguiente código muestra un ejemplo completo de cómo iniciar sesión como usuario multifactor:

API modular web

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.
        }
    });

API con espacio de nombres web

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.
    } ...
  });

¡Felicidades! Ingresó exitosamente a un usuario mediante la autenticación multifactor.

Que sigue