Como adicionar a autenticação multifator ao seu app para Android

Este documento mostra como adicionar a autenticação multifator por SMS ao seu app Android.

A autenticação multifator aumenta a segurança do aplicativo. Os invasores costumam comprometer as senhas e contas de redes sociais, mas é mais difícil interceptar uma mensagem de texto.

Antes de começar

  1. Ative um provedor que ofereça suporte à autenticação multifator. São eles:

    • E-mail e senha
    • Enviar link por e-mail
    • Google
    • Google Play
    • Facebook
    • Twitter
    • GitHub
    • Microsoft
    • Yahoo
    • LinkedIn
  2. Confirme se o app está verificando os e-mails do usuário. A autenticação multifator (MFA) requer verificação de e-mail. Isso impede que agentes mal-intencionados se registrem em um serviço com um e-mail que não tenham e bloqueiem o proprietário real adicionando um segundo fator.

  3. Registre o hash SHA-1 do seu app no Console do Firebase (suas alterações serão automaticamente transferidas para o Google Cloud Identity Platform).

    1. Siga as etapas em Como autenticar seu cliente para receber o hash SHA-1 do seu app.

    2. Abra o Console do Firebase.

    3. Navegue até Configurações do projeto.

    4. Nos Apps, clique no ícone do Android.

    5. Siga as etapas guiadas para adicionar seu hash SHA-1.

Como ativar a autenticação multifator

  1. Acesse a página Identity Platform MFA no Console do Cloud.
    Acessar a página MFA

  2. Na caixa Autenticação de vários fatores baseada em SMS, clique em Ativar.

  3. Insira os números de telefone com os quais você testará seu aplicativo. Ainda que seja opcional, é altamente recomendável registrar números de telefone de teste para evitar a limitação durante o desenvolvimento.

  4. Se você ainda não autorizou o domínio do seu aplicativo, adicione-o à lista de permissões clicando em Adicionar domínio à direita.

  5. Clique em Save.

Como escolher um padrão de inscrição

Você pode escolher se o app requer autenticação multifator e como e quando inscrever os usuários. Alguns padrões comuns incluem:

  • Inscrever o segundo fator do usuário como parte do registro. Use esse método se o aplicativo exigir autenticação multifator para todos os usuários.

  • Ofereça uma opção pulável para inscrever um segundo fator durante o registro. Os apps que quiserem incentivar, mas não exigirem, a autenticação multifator terão essa abordagem.

  • Ofereça a capacidade de adicionar um segundo fator da página de gerenciamento da conta ou do perfil do usuário, em vez da tela de inscrição. Isso minimiza o atrito durante o processo de registro, enquanto disponibilizando a autenticação multifator para usuários sensíveis à segurança.

  • Exija a adição de um segundo fator de maneira incremental quando o usuário quiser acessar recursos com requisitos de segurança aprimorados.

Como inscrever um segundo fator

Para inscrever um novo fator secundário para um usuário:

  1. Re-autentique o usuário.

  2. Peça ao usuário para inserir o número de telefone.

  3. Receba uma sessão de vários fatores para o usuário:

    user.getMultiFactor().getSession()
       .addOnCompleteListener(
          new OnCompleteListener<MultiFactorSession>() {
           @Override
           public void onComplete(@NonNull Task<MultiFactorSession> task) {
             if (task.isSuccessful()) {
              MultiFactorSession multiFactorSession = task.getResult();
             }
           }
          });
    
  4. Crie um objeto OnVerificationStateChangedCallbacks para lidar com eventos diferentes no processo de verificação:

    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. Inicialize um objeto PhoneInfoOptions com o número de telefone do usuário, a sessão de vários fatores e seus callbacks:

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

    Por padrão, a verificação instantânea está ativada. Para desativá-lo, adicione uma chamada a requireSmsValidation(true).

  6. Enviar uma mensagem de verificação para o telefone do usuário:

    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
    

    Ainda que não seja obrigatório, é uma prática recomendada informar aos usuários antecipadamente que eles receberão uma mensagem SMS e quais taxas padrão serão aplicadas.

  7. Quando o código SMS for enviado, peça ao usuário para verificar o código:

    // Ask user for the verification code.
    PhoneAuthCredential credential
       = PhoneAuthProvider.getCredential(verificationId, verificationCode);
    
  8. Inicialize um objeto MultiFactorAssertion com o PhoneAuthCredential:

    MultiFactorAssertion multiFactorAssertion
       = PhoneMultiFactorGenerator.getAssertion(credential);
    
  9. Conclua a inscrição. Se quiser, é possível especificar um nome de exibição para o segundo fator. Isso é útil para usuários com vários fatores, já que o número de telefone é mascarado durante o fluxo de autenticação (por exemplo, +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) {
             // ...
           }
          });
    

O código abaixo mostra um exemplo completo de inscrição de um segundo fator:

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

Parabéns! Você registrou um segundo fator de autenticação para um usuário.

Como fazer login dos usuários com um segundo fator

Para fazer login de um usuário com verificação por SMS de dois fatores:

  1. Faça login do usuário com o primeiro fator e, em seguida, detecte a exceção FirebaseAuthMultiFactorException. Esse erro contém um resolvedor, que pode ser usado para conseguir o segundo fator registrado do usuário. Ele também contém uma sessão subjacente que prova o autenticação do usuário com o primeiro fator.

    Por exemplo, se o primeiro fator do usuário for um e-mail e uma senha:

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

    Se o primeiro fator do usuário for um provedor federado, como OAuth, capturar o erro depois de chamar startActivityForSignInWithProvider().

  2. Se o usuário tiver vários fatores secundários registrados, pergunte a ele qual 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. Inicialize um objeto PhoneAuthOptions com a dica e a sessão de vários fatores. Esses valores estão contidos no resolvedor anexado ao FirebaseAuthMultiFactorException.

    PhoneAuthOptions phoneAuthOptions =
       PhoneAuthOptions.newBuilder()
          .setMultiFactorHint(selectedHint)
          .setTimeout(30L, TimeUnit.SECONDS)
          .setMultiFactorSession(multiFactorResolver.getSession())
          .setCallbacks(callbacks)
          // Optionally disable instant verification.
          // .requireSmsValidation(true)
          .build();
    
  4. Enviar uma mensagem de verificação para o telefone do usuário:

    // Send SMS verification code
    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
    
  5. Quando o código SMS for enviado, peça ao usuário para verificar o código:

    // Ask user for the verification code.
    PhoneAuthCredential credential
        = PhoneAuthProvider.getCredential(verificationId, verificationCode);
    
  6. Inicialize um objeto MultiFactorAssertion com o PhoneAuthCredential:

    MultiFactorAssertion multiFactorAssertion
        = PhoneMultiFactorGenerator.getAssertion(credential);
    
  7. Chame resolver.resolveSignIn() para concluir a autenticação secundária. É possível acessar o resultado de login original, que inclui as credenciais padrão de autenticação e dados específicos do provedor:

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

O código abaixo mostra um exemplo completo de login em um usuário de vários fatores:

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

Parabéns! Você fez login com sucesso usando uma autenticação multifator.

A seguir