Agregar autenticación de varios factores a una app para Android

En este documento, se muestra cómo agregar una autenticación de varios factores por SMS a tu app para Android.

La autenticación de varios factores aumenta la seguridad de tu app. Si bien los atacantes a menudo hackean las contraseñas y las cuentas en plataformas sociales, interceptar un mensaje de texto es más difícil.

Antes de comenzar

  1. Habilita un proveedor que admita la autenticación de varios factores. Estos incluyen:

    • Correo electrónico y contraseña
    • Enviar vínculo por correo electrónico
    • Google
    • Google Play
    • Facebook
    • Twitter
    • GitHub
    • Microsoft
    • Yahoo
    • LinkedIn
  2. Asegúrate de que tu app verifique los correos electrónicos de los usuarios. La MFA requiere la verificación por correo electrónico. Esto evita que los actores maliciosos se registren en un servicio con un correo electrónico que no les pertenece y, luego, bloqueen al propietario real mediante el agregado de un segundo factor.

  3. Registra el hash SHA-1 de tu app en Firebase Console (tus cambios se transferirán automáticamente a Google Cloud Identity Platform).

    1. Sigue los pasos que se indican en Autentica tu cliente para obtener el hash SHA-1 de tu app.

    2. Abre Firebase console.

    3. Navega hasta Configuración del proyecto.

    4. En Tus apps, haz clic en el ícono de Android.

    5. Sigue los pasos guiados para agregar tu hash SHA-1.

Habilita la autenticación de varios factores

  1. Ve a la página MFA de Identity Platform en Cloud Console.
    Ir a la página MFA

  2. En el cuadro llamado Autenticación de varios factores basada en SMS, haz clic en Habilitar.

  3. Ingresa los números de teléfono con los que probarás tu app. Si bien es opcional, se recomienda registrar los números de teléfono de prueba para evitar los límites durante el desarrollo.

  4. Si aún no autorizaste el dominio de tu app, haz clic en Agregar dominio a la derecha para agregarlo a la lista de permisos.

  5. Haga clic en Save.

Elige un patrón de inscripción

Puedes elegir si tu app requiere una autenticación de varios factores, además de cómo y cuándo inscribir a tus usuarios. Algunos patrones comunes incluyen los siguientes:

  • Inscribir el segundo factor del usuario como parte del registro. Usa este método si tu aplicación requiere la autenticación de varios factores para todos los usuarios.

  • Ofrecer una opción que se puede omitir para inscribir un segundo factor durante el registro. Es posible que las apps que quieran fomentar el proceso de autenticación de varios factores, pero que no lo requieran, prefieran este enfoque.

  • Proporcionar la capacidad de agregar un segundo factor desde la página de administración de la cuenta o el perfil del usuario, en lugar de la pantalla de registro. Esto minimiza la fricción durante el proceso de registro y, a la vez, permite que la autenticación de varios factores esté disponible para los usuarios sensibles a la seguridad.

  • Requiere agregar un segundo factor de manera incremental cuando el usuario quiera acceder a las funciones con requisitos de seguridad mayores.

Inscribe un segundo factor

Para inscribir un nuevo factor secundario para un usuario, haz lo siguiente:

  1. Vuelve a autenticar al usuario.

  2. Pídele al usuario que ingrese su número de teléfono.

  3. Obtén una sesión de varios factores para el usuario:

    user.getMultiFactor().getSession()
       .addOnCompleteListener(
          new OnCompleteListener<MultiFactorSession>() {
           @Override
           public void onComplete(@NonNull Task<MultiFactorSession> task) {
             if (task.isSuccessful()) {
              MultiFactorSession multiFactorSession = task.getResult();
             }
           }
          });
    
  4. Compila un objeto OnVerificationStateChangedCallbacks para manejar diferentes eventos en el proceso de verificación:

    OnVerificationStateChangedCallbacks callbacks =
     new OnVerificationStateChangedCallbacks() {
      @Override
      public void onVerificationCompleted(PhoneAuthCredential credential) {
        // This callback will be invoked in two situations:
        // 1) Instant verification. In some cases, the phone number can be
        //    instantly verified without needing to send or enter a verification
        //    code. You can disable this feature by calling
        //    PhoneAuthOptions.builder#requireSmsValidation(true) when building
        //    the options to pass to PhoneAuthProvider#verifyPhoneNumber().
        // 2) Auto-retrieval. On some devices, Google Play services can
        //    automatically detect the incoming verification SMS and perform
        //    verification without user action.
        this.credential = credential;
      }
      @Override
      public void onVerificationFailed(FirebaseException e) {
        // This callback is invoked in response to invalid requests for
        // verification, like an incorrect phone number.
        if (e instanceof FirebaseAuthInvalidCredentialsException) {
         // Invalid request
         // ...
        } else if (e instanceof FirebaseTooManyRequestsException) {
         // The SMS quota for the project has been exceeded
         // ...
        }
        // Show a message and update the UI
        // ...
      }
      @Override
      public void onCodeSent(
         String verificationId, PhoneAuthProvider.ForceResendingToken token) {
        // The SMS verification code has been sent to the provided phone number.
        // We now need to ask the user to enter the code and then construct a
        // credential by combining the code with a verification ID.
        // Save the verification ID and resending token for later use.
        this.verificationId = verificationId;
        this.forceResendingToken = token;
        // ...
      }
    };
    
  5. Inicializa un objeto PhoneInfoOptions con el número de teléfono del usuario, la sesión de varios factores y tus devoluciones de llamada:

    PhoneAuthOptions phoneAuthOptions =
       PhoneAuthOptions.newBuilder()
          .setPhoneNumber(phoneNumber)
          .setTimeout(30L, TimeUnit.SECONDS)
          .setMultiFactorSession(MultiFactorSession)
          .setCallbacks(callbacks)
          .build();
    

    De forma predeterminada, la verificación instantánea se encuentra habilitada. Para inhabilitarla, agrega una llamada a requireSmsValidation(true).

  6. Envía un mensaje de verificación al teléfono del usuario:

    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
    

    Si bien no es obligatorio, se recomienda informar con anticipación a los usuarios que recibirán un mensaje SMS y que se aplicarán las tarifas estándar.

  7. Una vez que se envía el código SMS, pídele al usuario que verifique el código:

    // Ask user for the verification code.
    PhoneAuthCredential credential
       = PhoneAuthProvider.getCredential(verificationId, verificationCode);
    
  8. Inicializa un objeto MultiFactorAssertion con la PhoneAuthCredential:

    MultiFactorAssertion multiFactorAssertion
       = PhoneMultiFactorGenerator.getAssertion(credential);
    
  9. Completa la inscripción. De manera opcional, puedes especificar un nombre visible para el segundo factor. Esto es útil para usuarios con varios segundos factores, ya que el número de teléfono se enmascara durante el flujo de autenticación (por ejemplo, +1****1234).

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    FirebaseAuth.getInstance()
       .getCurrentUser()
       .getMultiFactor()
       .enroll(multiFactorAssertion, "My personal phone number")
       .addOnCompleteListener(
          new OnCompleteListener<Void>() {
           @Override
           public void onComplete(@NonNull Task<Void> task) {
             // ...
           }
          });
    

En el siguiente código, se muestra un ejemplo completo de la inscripción de un segundo factor:

MultiFactorAssertion multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential);
user.getMultiFactor().getSession()
   .addOnCompleteListener(
      new OnCompleteListener<MultiFactorSession>() {
       @Override
       public void onComplete(@NonNull Task<MultiFactorSession> task) {
         if (task.isSuccessful()) {
          MultiFactorSession multiFactorSession = task.getResult();
          PhoneAuthOptions phoneAuthOptions =
             PhoneAuthOptions.newBuilder()
                .setPhoneNumber(phoneNumber)
                .setTimeout(30L, TimeUnit.SECONDS)
                .setMultiFactorSession(multiFactorSession)
                .setCallbacks(callbacks)
                .build();
          // Send SMS verification code.
          PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
         }
       }
      });

// Ask user for the verification code.
PhoneAuthCredential credential =
   PhoneAuthProvider.getCredential(verificationId, verificationCode);

MultiFactorAssertion multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential);
// Complete enrollment.
FirebaseAuth.getInstance()
   .getCurrentUser()
   .getMultiFactor()
   .enroll(multiFactorAssertion, "My personal phone number")
   .addOnCompleteListener(
      new OnCompleteListener<Void>() {
       @Override
       public void onComplete(@NonNull Task<Void> task) {
         // ...
       }
      });

Felicitaciones Registraste correctamente un segundo factor de autenticación para un usuario.

Permite que los usuarios accedan con un segundo factor

Para que un usuario acceda con la verificación por SMS de dos factores, haz lo siguiente:

  1. Haz que el usuario acceda con el primer factor y, luego, detecta la excepción FirebaseAuthMultiFactorException. Este error contiene un agente de resolución, que puedes usar para obtener los segundos factores inscritos del usuario. También contiene una sesión subyacente que demuestra que el usuario se autenticó correctamente con su primer factor.

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

    FirebaseAuth.getInstance()
       .signInWithEmailAndPassword(email, password)
       .addOnCompleteListener(
          new OnCompleteListener<AuthResult>() {
           @Override
           public void onComplete(@NonNull Task<AuthResult> task) {
             if (task.isSuccessful()) {
              // 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.
              MultiFactorResolver multiFactorResolver = task.getException().getResolver();
              // ...
             } else {
              // Handle other errors such as wrong password.
             }
           }
          });
    

    Si el primer factor del usuario es un proveedor federado, como OAuth, detecta el error después de llamar a startActivityForSignInWithProvider().

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

    // Ask user which second factor to use.
    // You can get the masked phone number using
    // resolver.getHints().get(selectedIndex).getPhoneNumber()
    // You can get the display name using
    // resolver.getHints().get(selectedIndex).getDisplayName()
    if (resolver.getHints().get(selectedIndex).getFactorId()
       == PhoneMultiFactorGenerator.FACTOR_ID) {
     // User selected a phone second factor.
     MultiFactorInfo selectedHint =
       multiFactorResolver.getHints().get(selectedIndex);
    } else {
     // Unsupported second factor.
     // Note that only phone second factors are currently supported.
    }
    
  3. Inicializa un objeto PhoneAuthOptions con la sugerencia y la sesión de varios factores. Estos valores se encuentran en el agente de resolución adjunto a FirebaseAuthMultiFactorException.

    PhoneAuthOptions phoneAuthOptions =
       PhoneAuthOptions.newBuilder()
          .setMultiFactorHint(selectedHint)
          .setTimeout(30L, TimeUnit.SECONDS)
          .setMultiFactorSession(multiFactorResolver.getSession())
          .setCallbacks(callbacks)
          // Optionally disable instant verification.
          // .requireSmsValidation(true)
          .build();
    
  4. Envía un mensaje de verificación al teléfono del usuario:

    // Send SMS verification code
    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
    
  5. Una vez que se envía el código SMS, pídele al usuario que verifique el código:

    // Ask user for the verification code.
    PhoneAuthCredential credential
        = PhoneAuthProvider.getCredential(verificationId, verificationCode);
    
  6. Inicializa un objeto MultiFactorAssertion con la PhoneAuthCredential:

    MultiFactorAssertion multiFactorAssertion
        = PhoneMultiFactorGenerator.getAssertion(credential);
    
  7. Llama a resolver.resolveSignIn() para completar la autenticación secundaria. Luego, puedes acceder al resultado del acceso original, en el que se incluyen los datos específicos del proveedor y las credenciales de autenticación estándar:

    multiFactorResolver
       .resolveSignIn(multiFactorAssertion)
       .addOnCompleteListener(
          new OnCompleteListener<AuthResult>() {
           @Override
           public void onComplete(@NonNull Task<AuthResult> task) {
             if (task.isSuccessful()) {
              AuthResult authResult = task.getResult();
              // AuthResult will also contain the user, additionalUserInfo,
              // and an 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, authResult.getAdditionalUserInfo() will contain data
              // related to Google provider that the user signed in with.
              // authResult.getCredential() will contain the Google OAuth
              // credential.
              // authResult.getCredential().getAccessToken() will contain the
              // Google OAuth access token.
              // authResult.getCredential().getIdToken() contains the Google
              // OAuth ID token.
             }
           }
          });
    

En el siguiente código, se muestra un ejemplo completo de acceso de un usuario de varios factores:

FirebaseAuth.getInstance()
   .signInWithEmailAndPassword(email, password)
   .addOnCompleteListener(
      new OnCompleteListener<AuthResult>() {
       @Override
       public void onComplete(@NonNull Task<AuthResult> task) {
         if (task.isSuccessful()) {
          // User is not enrolled with a second factor and is successfully
          // signed in.
          // ...
          return;
         }
         if (task.getException() instanceof FirebaseAuthMultiFactorException) {
          FirebaseAuthMultiFactorException e =
            (FirebaseAuthMultiFactorException) task.getException();

          MultiFactorResolver multiFactorResolver = e.getResolver();

          // Ask user which second factor to use.
          MultiFactorInfo selectedHint =
             multiFactorResolver.getHints().get(selectedIndex);

          // Send the SMS verification code.
          PhoneAuthProvider.verifyPhoneNumber(
             PhoneAuthOptions.newBuilder()
                .setActivity(this)
                .setMultiFactorSession(multiFactorResolver.getSession())
                .setMultiFactorHint(selectedtHint)
                .setCallbacks(generateCallbacks())
                .setTimeout(30L, TimeUnit.SECONDS)
                .build());

          // Ask user for the SMS verification code.
          PhoneAuthCredential credential =
             PhoneAuthProvider.getCredential(verificationId, verificationCode);

          // Initialize a MultiFactorAssertion object with the
          // PhoneAuthCredential.
          MultiFactorAssertion multiFactorAssertion =
             PhoneMultiFactorGenerator.getAssertion(credential);

          // 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
                     // second factor phone number.
                   }
                   // ...
                  }
                });
         } else {
          // Handle other errors such as wrong password.
         }
       }
      });

Felicitaciones Accediste correctamente a un usuario mediante la autenticación de varios factores.

Próximos pasos