Añadir autenticación multifactor a una aplicación web
En este documento se explica cómo añadir la autenticación multifactor por SMS a una aplicación web.
La autenticación multifactor aumenta la seguridad de tu aplicación. Aunque los atacantes suelen vulnerar las contraseñas y las cuentas de redes sociales, interceptar mensajes de texto resulta más difícil.
Antes de empezar
Habilita al menos un proveedor que admita la autenticación multifactor. Todos los proveedores admiten la MFA, excepto la autenticación por teléfono, la autenticación anónima y Game Center de Apple.
Habilita las regiones en las que quieras usar la autenticación por SMS. Identity Platform usa una política de regiones de SMS totalmente bloqueadora, lo que ayuda a crear tus proyectos en un estado más seguro de forma predeterminada.
Asegúrate de que tu aplicación verifica los correos de los usuarios. La MFA requiere verificación por correo electrónico. De esta forma, se impide que agentes malintencionados se registren en un servicio con un correo que no les pertenece y, a continuación, bloqueen al propietario real añadiendo un segundo factor.
Usar la arquitectura multicliente
Si vas a habilitar la autenticación multifactor para usarla en un entorno multitenant, asegúrate de completar los siguientes pasos (además del resto de las instrucciones de este documento):
En la Google Cloud consola, selecciona el arrendatario con el que quieras trabajar.
En tu código, asigna el valor del campo
tenantId
de la instanciaAuth
al ID de tu arrendatario. Por ejemplo:Versión web 9
import { getAuth } from "firebase/auth"; const auth = getAuth(app); auth.tenantId = "myTenantId1";
Versión web 8
firebase.auth().tenantId = 'myTenantId1';
Habilitar la autenticación multifactor
Ve a la página MFA de Identity Platform en la Google Cloud consola.
Ve a la página MFAEn el cuadro Autenticación multifactor por SMS, haz clic en Habilitar.
Introduce los números de teléfono con los que vas a probar tu aplicación. Aunque es opcional, te recomendamos que registres números de teléfono de prueba para evitar la limitación durante el desarrollo.
Si aún no has autorizado el dominio de tu aplicación, añádelo a la lista de permitidos haciendo clic en Añadir dominio, a la derecha.
Haz clic en Guardar.
Elegir un patrón de registro
Puedes elegir si tu aplicación requiere la autenticación multifactor y cómo y cuándo registrar a tus usuarios. Estos son algunos de los patrones habituales:
Registra el segundo factor del usuario durante el registro. Usa este método si tu aplicación requiere la autenticación multifactor para todos los usuarios.
Ofrece una opción para omitir el registro de un segundo factor durante el registro. Las aplicaciones que quieran fomentar la autenticación multifactor, pero no exigirla, pueden preferir este método.
Ofrecer la posibilidad de añadir un segundo factor desde la página de gestión de la cuenta o el perfil del usuario, en lugar de desde la pantalla de registro. De esta forma, se minimizan las fricciones durante el proceso de registro y, al mismo tiempo, se ofrece la autenticación multifactor a los usuarios que se preocupan por la seguridad.
Exigir que se añada un segundo factor de forma gradual cuando el usuario quiera acceder a funciones con requisitos de seguridad más estrictos.
Configurar el verificador de reCAPTCHA
Para poder enviar códigos por SMS, debes configurar un verificador reCAPTCHA. Identity Platform usa reCAPTCHA para evitar abusos. Para ello, se asegura de que las solicitudes de verificación de números de teléfono procedan de uno de los dominios permitidos de tu aplicación.
No es necesario que configures manualmente un cliente de reCAPTCHA. El objeto RecaptchaVerifier
del SDK de cliente crea e inicializa automáticamente las claves y los secretos de cliente necesarios.
Usar reCAPTCHA invisible
El objeto RecaptchaVerifier
admite reCAPTCHA invisible, que a menudo puede verificar al usuario sin necesidad de que interactúe. Para usar un reCAPTCHA invisible, crea un RecaptchaVerifier
con el parámetro size
definido como invisible
y especifica el ID del elemento de la interfaz de usuario que inicia el registro de la autenticación multifactor:
Versión web 9
import { RecaptchaVerifier, getAuth } from "firebase/auth";
const recaptchaVerifier = new RecaptchaVerifier(getAuth(), "sign-in-button", {
"size": "invisible",
"callback": function(response) {
// reCAPTCHA solved, you can proceed with
// phoneAuthProvider.verifyPhoneNumber(...).
onSolvedRecaptcha();
}
});
Versión 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();
}
});
Usar el widget reCAPTCHA
Para usar un widget de reCAPTCHA visible, crea un elemento HTML que contenga el widget y, a continuación, crea un objeto RecaptchaVerifier
con el ID del contenedor de la interfaz de usuario. También puedes definir de forma opcional retrollamadas que se invoquen cuando se resuelva o caduque reCAPTCHA:
Versión web 9
import { RecaptchaVerifier, getAuth } from "firebase/auth";
const recaptchaVerifier = new RecaptchaVerifier(
getAuth(),
"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.
// ...
}
}
);
Versión 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.
// ...
}
});
Renderizar previamente reCAPTCHA
También puedes renderizar previamente reCAPTCHA antes de iniciar la verificación en dos pasos:
Versión web 9
recaptchaVerifier.render()
.then(function (widgetId) {
window.recaptchaWidgetId = widgetId;
});
Versión web 8
recaptchaVerifier.render()
.then(function(widgetId) {
window.recaptchaWidgetId = widgetId;
});
Una vez que se resuelve render()
, obtienes el ID del widget de reCAPTCHA, que puedes usar para hacer llamadas a la API de reCAPTCHA:
var recaptchaResponse = grecaptcha.getResponse(window.recaptchaWidgetId);
RecaptchaVerifier abstrae esta lógica con el método verify, por lo que no es necesario que gestione la variable grecaptcha
directamente.
Registrar un segundo factor
Para registrar un nuevo factor secundario para un usuario, sigue estos pasos:
Vuelve a autenticar al usuario.
Pide al usuario que introduzca su número de teléfono.
Inicializa el verificador reCAPTCHA como se muestra en la sección anterior. Salta este paso si ya has configurado una instancia de RecaptchaVerifier:
Versión web 9
import { RecaptchaVerifier, getAuth } from "firebase/auth"; const recaptchaVerifier = new RecaptchaVerifier( getAuth(),'recaptcha-container-id', undefined);
Versión web 8
var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
Obtener una sesión multifactor para el usuario:
Versión web 9
import { multiFactor } from "firebase/auth"; multiFactor(user).getSession().then(function (multiFactorSession) { // ... });
Versión web 8
user.multiFactor.getSession().then(function(multiFactorSession) { // ... })
Inicializa un objeto
PhoneInfoOptions
con el número de teléfono del usuario y la sesión multifactor:Versión web 9
// Specify the phone number and pass the MFA session. const phoneInfoOptions = { phoneNumber: phoneNumber, session: multiFactorSession };
Versión web 8
// Specify the phone number and pass the MFA session. var phoneInfoOptions = { phoneNumber: phoneNumber, session: multiFactorSession };
Envía un mensaje de verificación al teléfono del usuario:
Versión 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. });
Versión 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. })
Aunque no es obligatorio, se recomienda informar a los usuarios de que van a recibir un mensaje SMS y de que se aplican tarifas estándar.
Si la solicitud falla, restablece reCAPTCHA y repite el paso anterior para que el usuario pueda intentarlo de nuevo. Ten en cuenta que
verifyPhoneNumber()
restablecerá automáticamente reCAPTCHA cuando se produzca un error, ya que los tokens de reCAPTCHA solo se pueden usar una vez.Versión web 9
recaptchaVerifier.clear();
Versión web 8
recaptchaVerifier.clear();
Una vez que se haya enviado el código por SMS, pide al usuario que lo verifique:
Versión web 9
// Ask user for the verification code. Then: const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
Versión web 8
// Ask user for the verification code. Then: var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
Inicializa un objeto
MultiFactorAssertion
conPhoneAuthCredential
:Versión web 9
import { PhoneMultiFactorGenerator } from "firebase/auth"; const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
Versión web 8
var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
Completa el registro. También puedes especificar un nombre visible para el segundo factor. Esto es útil para los usuarios que tienen varios segundos factores, ya que el número de teléfono se oculta durante el flujo de autenticación (por ejemplo, +1******1234).
Versión web 9
// Complete enrollment. This will update the underlying tokens // and trigger ID token change listener. multiFactor(user).enroll(multiFactorAssertion, "My personal phone number");
Versión web 8
// 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:
Versión web 9
import {
multiFactor, PhoneAuthProvider, PhoneMultiFactorGenerator,
RecaptchaVerifier, getAuth
} from "firebase/auth";
const recaptchaVerifier = new RecaptchaVerifier(getAuth(),
'recaptcha-container-id', undefined);
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);
});
Versión 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);
});
¡Enhorabuena! Has registrado correctamente un segundo factor de autenticación para un usuario.
Inicio de sesión de usuarios con un segundo factor
Para iniciar la sesión de un usuario con la verificación por SMS de dos factores, sigue estos pasos:
Inicia sesión del usuario con su primer factor y, a continuación, detecta el error
auth/multi-factor-auth-required
. Este error contiene un resolver, sugerencias sobre los segundos factores registrados y una sesión subyacente que demuestra que el usuario se ha autenticado correctamente con el primer factor.Por ejemplo, si el primer factor del usuario era un correo y una contraseña:
Versión web 9
import { getAuth, signInWithEmailAndPassword, 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. } });
Versión 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 el primer factor del usuario es un proveedor federado, como OAuth, SAML u OIDC, detecta el error después de llamar a
signInWithPopup()
osignInWithRedirect()
.Si el usuario tiene registrados varios factores secundarios, pregúntale cuál quiere usar:
Versión 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. }
Versión 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. }
Inicializa el verificador reCAPTCHA como se muestra en la sección anterior. Salta este paso si ya has configurado una instancia de RecaptchaVerifier:
Versión web 9
import { RecaptchaVerifier, getAuth } from "firebase/auth"; recaptchaVerifier = new RecaptchaVerifier(getAuth(), 'recaptcha-container-id', undefined);
Versión web 8
var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
Inicializa un objeto
PhoneInfoOptions
con el número de teléfono del usuario y la sesión multifactor. Estos valores se incluyen en el objetoresolver
que se ha pasado al errorauth/multi-factor-auth-required
:Versión web 9
const phoneInfoOptions = { multiFactorHint: resolver.hints[selectedIndex], session: resolver.session };
Versión web 8
var phoneInfoOptions = { multiFactorHint: resolver.hints[selectedIndex], session: resolver.session };
Envía un mensaje de verificación al teléfono del usuario:
Versión 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. });
Versión 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. })
Si la solicitud falla, restablece reCAPTCHA y repite el paso anterior para que el usuario pueda volver a intentarlo:
Versión web 9
recaptchaVerifier.clear();
Versión web 8
recaptchaVerifier.clear();
Una vez que se haya enviado el código por SMS, pide al usuario que lo verifique:
Versión web 9
const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
Versión web 8
// Ask user for the verification code. Then: var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
Inicializa un objeto
MultiFactorAssertion
conPhoneAuthCredential
:Versión web 9
const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
Versión web 8
var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
Llama al
resolver.resolveSignIn()
para completar la autenticación secundaria. Después, puedes acceder al resultado del inicio de sesión original, que incluye los datos específicos del proveedor y las credenciales de autenticación estándar:Versión 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. });
Versión 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. });
El código siguiente muestra un ejemplo completo de inicio de sesión de un usuario con varios factores:
Versión web 9
import {
getAuth,
getMultiFactorResolver,
PhoneAuthProvider,
PhoneMultiFactorGenerator,
RecaptchaVerifier,
signInWithEmailAndPassword
} from "firebase/auth";
const recaptchaVerifier = new RecaptchaVerifier(getAuth(),
'recaptcha-container-id', undefined);
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.
}
});
Versión 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.
} ...
});
¡Enhorabuena! Has iniciado sesión correctamente con un usuario mediante la autenticación multifactor.
Siguientes pasos
- Gestiona usuarios que utilicen la autenticación multifactor de forma programática con el SDK de administrador.