Habilitar la MFA TOTP en tu aplicación
En este documento se describe cómo añadir la autenticación multifactor (MFA) con contraseña temporal de un solo uso (TOTP) a tu aplicación.
Identity Platform te permite usar una TOTP como factor adicional para la MFA. Cuando habilitas esta función, los usuarios que intentan iniciar sesión en tu aplicación ven una solicitud de TOTP. Para generarlo, deben usar una aplicación de autenticación que pueda generar códigos TOTP válidos, como Google Authenticator.
Antes de empezar
Habilita al menos un proveedor que admita la MFA. Ten en cuenta que todos los proveedores excepto los siguientes admiten la autenticación multifactor:
- Autenticación por teléfono
- Autenticación anónima
- Tokens de autenticación personalizados
- Game Center de Apple
Asegúrate de que tu aplicación verifique las direcciones de correo de los usuarios. La MFA requiere verificación por correo electrónico. De esta forma, se evita que agentes maliciosos se registren en un servicio con una dirección de correo que no les pertenece y, después, impidan el acceso al propietario real de la dirección de correo añadiendo un segundo factor.
Asegúrate de que tienes la versión de la plataforma correcta. La MFA con TOTP solo se admite en las siguientes versiones del SDK:
Plataforma Versiones SDK web v9.19.1 o versiones posteriores SDK de Android 22.1.0+ SDK de iOS 10.12.0+
Habilitar la MFA TOTP a nivel de proyecto
Para habilitar TOTP como segundo factor, usa el SDK de administrador o llama al endpoint REST de configuración del proyecto.
Para usar el SDK de administrador, haz lo siguiente:
Si aún no lo has hecho, instala el SDK de Node.js de administrador de Firebase.
La MFA con TOTP solo se admite en las versiones 11.6.0 y posteriores del SDK de Node.js de administrador de Firebase.
Ejecuta lo siguiente:
import { getAuth } from 'firebase-admin/auth'; getAuth().projectConfigManager().updateProjectConfig( { multiFactorConfig: { providerConfigs: [{ state: "ENABLED", totpProviderConfig: { adjacentIntervals: NUM_ADJ_INTERVALS } }] } })
Haz los cambios siguientes:
NUM_ADJ_INTERVALS
: número de intervalos de ventana temporal adyacentes desde los que se aceptan las TOTPs, de cero a diez. El valor predeterminado es cinco.Las TOTPs funcionan de forma que, cuando dos partes (el demostrador y el validador) generan OTPs en el mismo periodo (normalmente, de 30 segundos), generan la misma contraseña. Sin embargo, para tener en cuenta la diferencia de hora entre las partes y el tiempo de respuesta de los usuarios, puedes configurar el servicio TOTP para que también acepte TOTPs de ventanas adyacentes.
Para habilitar la MFA TOTP mediante la API REST, ejecuta lo siguiente:
curl -X PATCH "https://identitytoolkit.googleapis.com/admin/v2/projects/PROJECT_ID/config?updateMask=mfa" \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
-H "X-Goog-User-Project: PROJECT_ID" \
-d \
'{
"mfa": {
"providerConfigs": [{
"state": "ENABLED",
"totpProviderConfig": {
"adjacentIntervals": NUM_ADJ_INTERVALS
}
}]
}
}'
Haz los cambios siguientes:
PROJECT_ID
: el ID del proyecto.NUM_ADJ_INTERVALS
: número de intervalos de la ventana temporal, de cero a diez. El valor predeterminado es cinco.Las TOTPs funcionan de forma que, cuando dos partes (el demostrador y el validador) generan OTPs en el mismo periodo (normalmente, de 30 segundos), generan la misma contraseña. Sin embargo, para tener en cuenta la diferencia de hora entre las partes y el tiempo de respuesta de los usuarios, puedes configurar el servicio TOTP para que también acepte TOTPs de ventanas adyacentes.
Habilitar la MFA TOTP a nivel de inquilino
Para habilitar TOTP como segundo factor de la MFA a nivel de inquilino, usa el siguiente código:
getAuth().tenantManager().updateTenant(TENANT_ID,
{
multiFactorConfig: {
state: 'ENABLED',
providerConfigs: [{
totpProviderConfig: {
adjacentIntervals: NUM_ADJ_INTERVALS
}
}]
}
})
Haz los cambios siguientes:
TENANT_ID
: ID de propietario de cadena.NUM_ADJ_INTERVALS
: número de intervalos de la ventana temporal, de cero a diez. El valor predeterminado es cinco.
Para habilitar la MFA TOTP mediante la API REST a nivel de arrendatario, ejecuta lo siguiente:
curl -X PATCH "https://identitytoolkit.googleapis.com/v2/projects/PROJECT_ID/tenants/TENANT_ID?updateMask=mfaConfig" \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
-H "X-Goog-User-Project: PROJECT_ID" \
-d \
'{
"mfaConfig": {
"providerConfigs": [{
"totpProviderConfig": {
"adjacentIntervals": NUM_ADJ_INTERVALS
}
}]
}
}'
Haz los cambios siguientes:
PROJECT_ID
: el ID del proyecto.TENANT_ID
: el ID de propietario.NUM_ADJ_INTERVALS
: número de intervalos de la ventana temporal, de cero a diez. El valor predeterminado es cinco.
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. Si quieres recomendar la autenticación multifactor en tu aplicación, pero no exigirla, puedes usar 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.
Registrar usuarios en la MFA TOTP
Después de habilitar la MFA TOTP como segundo factor de autenticación para tu aplicación, implementa la lógica del lado del cliente para registrar a los usuarios en la MFA TOTP:
Web
import {
multiFactor,
TotpMultiFactorGenerator,
TotpSecret
} from "firebase/auth";
multiFactorSession = await multiFactor(activeUser()).getSession();
totpSecret = await TotpMultiFactorGenerator.generateSecret(
multiFactorSession
);
// Display this URL as a QR code.
const url = totpSecret.generateQrCodeUrl( <user account id> , <app name> );
// Ask the user for the verification code from the OTP app by scanning the QR
// code.
const multiFactorAssertion = TotpMultiFactorGenerator.assertionForEnrollment(
totpSecret,
verificationCode
);
// Finalize the enrollment.
return multiFactor(user).enroll(multiFactorAssertion, mfaDisplayName);
Java
user.getMultiFactor().getSession()
.addOnCompleteListener(
new OnCompleteListener<MultiFactorSession>() {
@Override
public void onComplete(@NonNull Task<MultiFactorSession> task) {
if (task.isSuccessful()) {
// Get a multi-factor session for the user.
MultiFactorSession multiFactorSession = task.getResult();
TotpMultiFactorGenerator.generateSecret(multiFactorSession)
.addOnCompleteListener(
new OnCompleteListener<TotpSecret>() {
@Override
public void onComplete(@NonNull Task<TotpSecret> task){
if (task.isSuccessful()) {
TotpSecret secret = task.getResult();
// Display this URL as a QR code for the user to scan.
String qrCodeUrl = secret.generateQrCodeUrl();
// Display the QR code
// ...
// Alternatively, you can automatically load the QR code
// into a TOTP authenticator app with either default or
// specified fallback URL and activity.
// Default fallback URL and activity.
secret.openInOtpApp(qrCodeUrl);
// Specified fallback URL and activity.
// secret.openInOtpApp(qrCodeUrl, fallbackUrl, activity);
}
}
});
}
}
});
// Ask the user for the one-time password (otp) from the TOTP authenticator app.
MultiFactorAssertion multiFactorAssertion =
TotpMultiFactorGenerator.getAssertionForEnrollment(
secret, otp);
// Complete the enrollment.
user
.getMultiFactor()
.enroll(multiFactorAssertion, /* displayName= */ "My TOTP second factor")
.addOnCompleteListener(
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
showToast("Successfully enrolled TOTP second factor!");
setResult(Activity.RESULT_OK);
finish();
}
}
});
Kotlin+KTX
user.multiFactor.session.addOnCompleteListener { task ->
if (task.isSuccessful) {
// Get a multi-factor session for the user.
val session: MultiFactorSession = task.result
val secret: TotpSecret = TotpMultiFactorGenerator.generateSecret(session)
// Display this URL as a QR code for the user to scan.
val qrCodeUrl = secret.generateQrCodeUrl()
// Display the QR code
// ...
// Alternatively, you can automatically load the QR code
// into a TOTP authenticator app with either default or
// specified fallback URL and activity.
// Default fallback URL and activity.
secret.openInOtpApp(qrCodeUrl)
// Specify a fallback URL and activity.
// secret.openInOtpApp(qrCodeUrl, fallbackUrl, activity);
}
}
// Ask the user for the one-time password (otp) from the TOTP authenticator app.
val multiFactorAssertion =
TotpMultiFactorGenerator.getAssertionForEnrollment(secret, otp)
// Complete enrollment.
user.multiFactor.enroll(multiFactorAssertion, /* displayName= */ "My TOTP second factor")
.addOnCompleteListener {
// ...
}
Swift
let user = Auth.auth().currentUser
// Get a multi-factor session for the user
user?.multiFactor.getSessionWithCompletion({ (session, error) in
TOTPMultiFactorGenerator.generateSecret(with: session!) {
(secret, error) in
let accountName = user?.email;
let issuer = Auth.auth().app?.name;
// Generate a QR code
let qrCodeUrl = secret?.generateQRCodeURL(withAccountName: accountName!, issuer: issuer!)
// Display the QR code
// ...
// Alternatively, you can automatically load the QR code
// into a TOTP authenticator app with default fallback UR
// and activity.
secret?.openInOTPAppWithQRCodeURL(qrCodeUrl!);
// Ask the user for the verification code after scanning
let assertion = TOTPMultiFactorGenerator.assertionForEnrollment(with: secret, oneTimePassword: onetimePassword)
// Complete the enrollment
user?.multiFactor.enroll(with: assertion, displayName: accountName) { (error) in
// ...
}
}
})
Objective‑C
FIRUser *user = FIRAuth.auth.currentUser;
// Get a multi-factor session for the user
[user.multiFactor getSessionWithCompletion:^(FIRMultiFactorSession *_Nullable session, NSError *_Nullable error) {
// ...
[FIRTOTPMultiFactorGenerator generateSecretWithMultiFactorSession:session completion:^(FIRTOTPSecret *_Nullable secret, NSError *_Nullable error) {
NSString *accountName = user.email;
NSString *issuer = FIRAuth.auth.app.name;
// Generate a QR code
NSString *qrCodeUrl = [secret generateQRCodeURLWithAccountName:accountName issuer:issuer];
// Display the QR code
// ...
// Alternatively, you can automatically load the QR code
// into a TOTP authenticator app with default fallback URL
// and activity.
[secret openInOTPAppWithQRCodeURL:qrCodeUrl];
// Ask the user for the verification code after scanning
FIRTOTPMultiFactorAssertion *assertion = [FIRTOTPMultiFactorGenerator assertionForEnrollmentWithSecret:secret oneTimePassword:oneTimePassword];
// Complete the enrollment
[user.multiFactor enrollWithAssertion:assertion
displayName:displayName
completion:^(NSError *_Nullable error) {
// ...
}];
}];
}];
Iniciar sesión de usuarios con un segundo factor
Para iniciar la sesión de los usuarios con la autenticación multifactor TOTP, usa el siguiente código:
Web
import {
getAuth,
getMultiFactorResolver,
TotpMultiFactorGenerator,
PhoneMultiFactorGenerator,
signInWithEmailAndPassword
} from "firebase/auth";
const auth = getAuth();
signInWithEmailAndPassword(auth, email, password)
.then(function(userCredential) {
// The 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 the user which second factor to use.
if (resolver.hints[selectedIndex].factorId ===
TotpMultiFactorGenerator.FACTOR_ID) {
// Ask the user for the OTP code from the TOTP app.
const multiFactorAssertion = TotpMultiFactorGenerator.assertionForSignIn(resolver.hints[selectedIndex].uid, otp);
// Finalize the sign-in.
return resolver.resolveSignIn(multiFactorAssertion).then(function(userCredential) {
// The user successfully signed in with the TOTP second factor.
});
} else if (resolver.hints[selectedIndex].factorId ===
PhoneMultiFactorGenerator.FACTOR_ID) {
// Handle the phone MFA.
} else {
// The second factor is unsupported.
}
}
// Handle other errors, such as a wrong password.
else if (error.code == 'auth/wrong-password') {
//...
}
});
Java
FirebaseAuth.getInstance()
.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(
new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
// The user is not enrolled with a second factor and is
// successfully signed in.
// ...
return;
}
if (task.getException() instanceof FirebaseAuthMultiFactorException) {
// The user is a multi-factor user. Second factor challenge is required.
FirebaseAuthMultiFactorException error =
(FirebaseAuthMultiFactorException) task.getException();
MultiFactorResolver multiFactorResolver = error.getResolver();
// Display the list of enrolled second factors, user picks one (selectedIndex) from the list.
MultiFactorInfo selectedHint = multiFactorResolver.getHints().get(selectedIndex);
if (selectedHint.getFactorId().equals(TotpMultiFactorGenerator.FACTOR_ID)) {
// Ask the user for the one-time password (otp) from the TOTP app.
// Initialize a MultiFactorAssertion object with the one-time password and enrollment id.
MultiFactorAssertion multiFactorAssertion =
TotpMultiFactorGenerator.getAssertionForSignIn(selectedHint.getUid(), otp);
// Complete sign-in.
multiFactorResolver
.resolveSignIn(multiFactorAssertion)
.addOnCompleteListener(
new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
// User successfully signed in with the
// TOTP second factor.
}
}
});
} else if (selectedHint.getFactorId().equals(PhoneMultiFactorGenerator.FACTOR_ID)) {
// Handle Phone MFA.
} else {
// Unsupported second factor.
}
} else {
// Handle other errors such as wrong password.
}
}
});
Kotlin+KTX
FirebaseAuth.getInstance
.signInWithEmailAndPassword(email, password)
.addOnCompleteListener{ task ->
if (task.isSuccessful) {
// User is not enrolled with a second factor and is successfully
// signed in.
// ...
}
if (task.exception is FirebaseAuthMultiFactorException) {
// The user is a multi-factor user. Second factor challenge is
// required.
val multiFactorResolver:MultiFactorResolver =
(task.exception as FirebaseAuthMultiFactorException).resolver
// Display the list of enrolled second factors, user picks one (selectedIndex) from the list.
val selectedHint: MultiFactorInfo = multiFactorResolver.hints[selectedIndex]
if (selectedHint.factorId == TotpMultiFactorGenerator.FACTOR_ID) {
val multiFactorAssertion =
TotpMultiFactorGenerator.getAssertionForSignIn(selectedHint.uid, otp)
multiFactorResolver.resolveSignIn(multiFactorAssertion)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
// User successfully signed in with the
// TOTP second factor.
}
// ...
}
} else if (selectedHint.factor == PhoneMultiFactorGenerator.FACTOR_ID) {
// Handle Phone MFA.
} else {
// Invalid MFA option.
}
} else {
// Handle other errors, such as wrong password.
}
}
Swift
Auth.auth().signIn(withEmail: email, password: password) {
(result, error) in
if (error != nil) {
let authError = error! as NSError
if authError.code == AuthErrorCode.secondFactorRequired.rawValue {
let resolver = authError.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver
if resolver.hints[selectedIndex].factorID == TOTPMultiFactorID {
let assertion = TOTPMultiFactorGenerator.assertionForSignIn(withEnrollmentID: resolver.hints[selectedIndex].uid, oneTimePassword: oneTimePassword)
resolver.resolveSignIn(with: assertion) {
(authResult, error) in
if (error != nil) {
// User successfully signed in with second factor TOTP.
}
}
} else if (resolver.hints[selectedIndex].factorID == PhoneMultiFactorID) {
// User selected a phone second factor.
// ...
} else {
// Unsupported second factor.
// Note that only phone and TOTP second factors are currently supported.
// ...
}
}
}
else {
// The user is not enrolled with a second factor and is
// successfully signed in.
// ...
}
}
Objective‑C
[FIRAuth.auth signInWithEmail:email
password:password
completion:^(FIRAuthDataResult * _Nullable authResult,
NSError * _Nullable error) {
if (error == nil || error.code != FIRAuthErrorCodeSecondFactorRequired) {
// User is not enrolled with a second factor and is successfully signed in.
// ...
} else {
// The user is a multi-factor user. Second factor challenge is required.
[self signInWithMfaWithError:error];
}
}];
- (void)signInWithMfaWithError:(NSError * _Nullable)error{
FIRMultiFactorResolver *resolver = error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];
// Ask user which second factor to use. Then:
FIRMultiFactorInfo *hint = (FIRMultiFactorInfo *) resolver.hints[selectedIndex];
if (hint.factorID == FIRTOTPMultiFactorID) {
// User selected a totp second factor.
// Ask user for verification code.
FIRMultiFactorAssertion *assertion = [FIRTOTPMultiFactorGenerator assertionForSignInWithEnrollmentID:hint.UID oneTimePassword:oneTimePassword];
[resolver resolveSignInWithAssertion:assertion
completion:^(FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error) {
if (error != nil) {
// User successfully signed in with the second factor TOTP.
}
}];
} else if (hint.factorID == FIRPhoneMultiFactorID) {
// User selected a phone second factor.
// ...
}
else {
// Unsupported second factor.
// Note that only phone and totp second factors are currently supported.
}
}
En el ejemplo anterior, se usan el correo y la contraseña como primer factor.
Darse de baja de la MFA con TOTP
En esta sección se describe cómo gestionar la cancelación de la autenticación multifactor con TOTP por parte de un usuario.
Si un usuario se ha registrado en varias opciones de MFA y se da de baja de la opción habilitada más recientemente, recibirá un auth/user-token-expired
y se cerrará su sesión. El usuario debe volver a iniciar sesión y verificar sus credenciales, como una dirección de correo y una contraseña.
Para dar de baja al usuario, gestionar el error y activar la reautenticación, usa el siguiente código:
Web
import {
EmailAuthProvider,
TotpMultiFactorGenerator,
getAuth,
multiFactor,
reauthenticateWithCredential,
} from "firebase/auth";
try {
// Unenroll from TOTP MFA.
await multiFactor(currentUser).unenroll(mfaEnrollmentId);
} catch (error) {
if (error.code === 'auth/user-token-expired') {
// If the user was signed out, re-authenticate them.
// For example, if they signed in with a password, prompt them to
// provide it again, then call `reauthenticateWithCredential()` as shown
// below.
const credential = EmailAuthProvider.credential(email, password);
await reauthenticateWithCredential(
currentUser,
credential
);
}
}
Java
List<MultiFactorInfo> multiFactorInfoList = user.getMultiFactor().getEnrolledFactors();
// Select the second factor to unenroll
user
.getMultiFactor()
.unenroll(selectedMultiFactorInfo)
.addOnCompleteListener(
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
// User successfully unenrolled the selected second factor.
}
else {
if (task.getException() instanceof FirebaseAuthInvalidUserException) {
// Handle reauthentication
}
}
}
});
Kotlin+KTX
val multiFactorInfoList = user.multiFactor.enrolledFactors
// Select the option to unenroll
user.multiFactor.unenroll(selectedMultiFactorInfo)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
// User successfully unenrolled the selected second factor.
}
else {
if (task.exception is FirebaseAuthInvalidUserException) {
// Handle reauthentication
}
}
}
Swift
user?.multiFactor.unenroll(with: (user?.multiFactor.enrolledFactors[selectedIndex])!,
completion: { (error) in
if (error.code == AuthErrorCode.userTokenExpired.rawValue) {
// Handle reauthentication
}
})
Objective‑C
FIRMultiFactorInfo *unenrolledFactorInfo;
for (FIRMultiFactorInfo *enrolledFactorInfo in FIRAuth.auth.currentUser.multiFactor.enrolledFactors) {
// Pick one of the enrolled factors to delete.
}
[FIRAuth.auth.currentUser.multiFactor unenrollWithInfo:unenrolledFactorInfo
completion:^(NSError * _Nullable error) {
if (error.code == FIRAuthErrorCodeUserTokenExpired) {
// Handle reauthentication
}
}];
Siguientes pasos
- Gestiona usuarios que utilicen la autenticación multifactor de forma programática con el SDK de administrador.