Adicione autenticação multifator ao seu aplicativo Android

Se você fez upgrade para o Firebase Authentication com Identity Platform, poderá adicionar a autenticação multifator por SMS ao seu aplicativo Android.

A autenticação multifator aumenta a segurança do seu aplicativo. Embora os invasores frequentemente comprometam senhas e contas sociais, interceptar uma mensagem de texto é mais difícil.

Antes de você começar

  1. Habilite pelo menos um provedor que ofereça suporte à autenticação multifator. Todos os provedores oferecem suporte a MFA, exceto autenticação por telefone, autenticação anônima e Apple Game Center.

  2. Certifique-se de que seu aplicativo esteja verificando os e-mails dos usuários. A MFA requer verificação de e-mail. Isso evita que atores mal-intencionados se registrem em um serviço com um e-mail que não sejam de sua propriedade e, em seguida, bloqueiem o proprietário real adicionando um segundo fator.

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

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

    2. Abra o Console do Firebase .

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

    4. Em Seus aplicativos , clique no ícone do Android.

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

Habilitando a autenticação multifator

  1. Abra a página Autenticação > Método de login do console do Firebase.

  2. Na seção Avançado , habilite a autenticação multifator de SMS .

    Você também deve inserir os números de telefone com os quais testará seu aplicativo. Embora opcional, é altamente recomendável registrar números de telefone de teste para evitar limitações durante o desenvolvimento.

  3. Se você ainda não autorizou o domínio do seu aplicativo, adicione-o à lista de permissões na página Autenticação > Configurações do console do Firebase.

Escolhendo um padrão de inscrição

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

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

  • Ofereça uma opção ignorável para inscrever um segundo fator durante o registro. Os aplicativos que desejam incentivar, mas não exigem, a autenticação multifator podem preferir essa abordagem.

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

  • Exigir a adição incremental de um segundo fator quando o usuário desejar acessar recursos com maiores requisitos de segurança.

Inscrevendo um segundo fator

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

  1. Autentique novamente o usuário.

  2. Peça ao usuário que insira seu número de telefone.

  3. Obtenha uma sessão multifatorial para o usuário:

    user.multiFactor.session.addOnCompleteListener { task ->
       
    if (task.isSuccessful) {
           
    val multiFactorSession: MultiFactorSession? = task.result
       
    }
    }
    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 diferentes eventos no processo de verificação:

    val callbacks = object : OnVerificationStateChangedCallbacks() {
       
    override fun onVerificationCompleted(credential: PhoneAuthCredential) {
           
    // 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@MainActivity.credential = credential
       
    }

       
    override fun onVerificationFailed(e: FirebaseException) {
           
    // This callback is invoked in response to invalid requests for
           
    // verification, like an incorrect phone number.
           
    if (e is FirebaseAuthInvalidCredentialsException) {
               
    // Invalid request
               
    // ...
           
    } else if (e is FirebaseTooManyRequestsException) {
               
    // The SMS quota for the project has been exceeded
               
    // ...
           
    }
           
    // Show a message and update the UI
           
    // ...
       
    }

       
    override fun onCodeSent(
            verificationId
    : String, forceResendingToken: ForceResendingToken
       
    ) {
           
    // 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@MainActivity.verificationId = verificationId
           
    this@MainActivity.forceResendingToken = forceResendingToken
           
    // ...
       
    }
    }
    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 multifator e seus retornos de chamada:

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

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

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

    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions)
    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);

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

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

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

    val multiFactorAssertion
     
    = PhoneMultiFactorGenerator.getAssertion(credential)
    MultiFactorAssertion multiFactorAssertion
     
    = PhoneMultiFactorGenerator.getAssertion(credential);
  9. Conclua a inscrição. Opcionalmente, você pode especificar um nome de exibição para o segundo fator. Isso é útil para usuários com vários segundos fatores, pois 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()
       
    .currentUser
       
    ?.multiFactor
       
    ?.enroll(multiFactorAssertion, "My personal phone number")
       
    ?.addOnCompleteListener {
           
    // ...
       
    }
    // 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:

val multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential)
user
.multiFactor.session
   
.addOnCompleteListener { task ->
       
if (task.isSuccessful) {
           
val multiFactorSession = task.result
           
val 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.
val credential = PhoneAuthProvider.getCredential(verificationId, verificationCode)

val multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential)

// Complete enrollment.
FirebaseAuth.getInstance()
   
.currentUser
   
?.multiFactor
   
?.enroll(multiFactorAssertion, "My personal phone number")
   
?.addOnCompleteListener {
       
// ...
   
}
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 com êxito um segundo fator de autenticação para um usuário.

Conectando usuários com um segundo fator

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

  1. Faça login do usuário com seu primeiro fator e, em seguida, capture a exceção FirebaseAuthMultiFactorException . Este erro contém um resolvedor, que pode ser usado para obter os segundos fatores registrados do usuário. Ele também contém uma sessão subjacente que prova que o usuário foi autenticado com sucesso com seu primeiro fator.

    Por exemplo, se o primeiro fator do usuário for email e senha:

    FirebaseAuth.getInstance()
       
    .signInWithEmailAndPassword(email, password)
       
    .addOnCompleteListener(
           
    OnCompleteListener { task ->
               
    if (task.isSuccessful) {
                   
    // User is not enrolled with a second factor and is successfully
                   
    // signed in.
                   
    // ...
                   
    return@OnCompleteListener
               
    }
               
    if (task.exception is FirebaseAuthMultiFactorException) {
                   
    // The user is a multi-factor user. Second factor challenge is
                   
    // required.
                   
    val multiFactorResolver =
                       
    (task.exception as FirebaseAuthMultiFactorException).resolver
                   
    // ...
               
    } else {
                   
    // Handle other errors, such as wrong password.
               
    }
           
    })
    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, detecte o erro após chamar startActivityForSignInWithProvider() .

  2. Se o usuário tiver vários fatores secundários inscritos, pergunte qual deles usar:

    // Ask user which second factor to use.
    // You can get the list of enrolled second factors using
    //   multiFactorResolver.hints

    // Check the selected factor:
    if (multiFactorResolver.hints[selectedIndex].factorId
       
    === PhoneMultiFactorGenerator.FACTOR_ID
    ) {
       
    // User selected a phone second factor.
       
    val selectedHint =
            multiFactorResolver
    .hints[selectedIndex] as PhoneMultiFactorInfo
    } else if (multiFactorResolver.hints[selectedIndex].factorId
       
    === TotpMultiFactorGenerator.FACTOR_ID) {
       
    // User selected a TOTP second factor.
    } else {
       
    // Unsupported second factor.
    }
    // 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()
                   
    .equals( PhoneMultiFactorGenerator.FACTOR_ID ) ) {
    // User selected a phone second factor.
    MultiFactorInfo selectedHint =
      multiFactorResolver
    .getHints().get(selectedIndex);
    } else if ( resolver
                 
    .getHints()
                 
    .get(selectedIndex)
                 
    .getFactorId()
                 
    .equals(TotpMultiFactorGenerator.FACTOR_ID ) ) {
     
    // User selected a TOTP second factor.
    } else {
    // Unsupported second factor.
    }
  3. Inicialize um objeto PhoneAuthOptions com a dica e a sessão multifator. Esses valores estão contidos no resolvedor anexado ao FirebaseAuthMultiFactorException .

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

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

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

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

    multiFactorResolver
       
    .resolveSignIn(multiFactorAssertion)
       
    .addOnCompleteListener { task ->
           
    if (task.isSuccessful) {
               
    val authResult = task.result
               
    // 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.
           
    }
       
    }
    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 de um usuário multifator:

FirebaseAuth.getInstance()
   
.signInWithEmailAndPassword(email, password)
   
.addOnCompleteListener { task ->
       
if (task.isSuccessful) {
           
// User is not enrolled with a second factor and is successfully
           
// signed in.
           
// ...
           
return@addOnCompleteListener
       
}
       
if (task.exception is FirebaseAuthMultiFactorException) {
           
val multiFactorResolver =
               
(task.exception as FirebaseAuthMultiFactorException).resolver

           
// Ask user which second factor to use. Then, get
           
// the selected hint:
           
val selectedHint =
                multiFactorResolver
.hints[selectedIndex] as PhoneMultiFactorInfo

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

           
// Ask user for the SMS verification code, then use it to get
           
// a PhoneAuthCredential:
           
val credential =
               
PhoneAuthProvider.getCredential(verificationId, verificationCode)

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

           
// Complete sign-in.
            multiFactorResolver
               
.resolveSignIn(multiFactorAssertion)
               
.addOnCompleteListener { task ->
                   
if (task.isSuccessful) {
                       
// User successfully signed in with the
                       
// second factor phone number.
                   
}
                   
// ...
               
}
       
} else {
           
// Handle other errors such as wrong password.
       
}
   
}
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(selectedHint)
               
.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 êxito em um usuário usando autenticação multifator.

Qual é o próximo