Como trabalhar com usuários de vários fatores

Neste documento, você aprenderá como realizar tarefas comuns com usuários do Identity Platform inscritos na autenticação multifator.

Como atualizar o e-mail de um usuário

Os usuários multifator precisam sempre ter um endereço de e-mail verificado. Isso impede que agentes mal-intencionados se inscrevam no seu app com um e-mail que não pertencem a eles e bloqueie o proprietário real adicionando um segundo fator.

Para atualizar o e-mail de um usuário, use o método verifyBeforeUpdateEmail(). Diferentemente de updateEmail(), este método requer que o usuário siga um link de verificação antes que o Identity Platform atualize o endereço de e-mail. Exemplo:

Versão 8 para a Web

var user = firebase.auth().currentUser;
user.verifyBeforeUpdateEmail(newEmail).then(function() {
  // Email sent.
  // User must click the email link before the email is updated.
}).catch(function(error) {
  // An error happened.
});

Versão 9 para a Web

import { getAuth, verifyBeforeUpdateEmail } from "firebase/auth";

const auth = getAuth(firebaseApp);
verifyBeforeUpdateEmail(auth.currentUser, newEmail).then(() => {
  // Email sent.
  // User must click the email link before the email is updated.
}).catch((error) => {
  // An error happened.
});

iOS

let user = Auth.auth().currentUser
user.verifyBeforeUpdateEmail(newEmail, completion: { (error) in
  if error != nil {
    // An error happened.
  }
  // Email sent.
  // User must click the email link before the email is updated.
})

Android

FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
user.verifyBeforeUpdateEmail(newEmail)
   .addOnCompleteListener(
      new OnCompleteListener<Void>() {
       @Override
       public void onComplete(@NonNull Task<Void> task) {
         if (task.isSuccessful()) {
          // Email sent.
          // User must click the email link before the email is updated.
         } else {
          // An error occurred.
         }
       }
      });

Por padrão, o Identity Platform envia um e-mail ao usuário e fornece um gerenciador simples, baseado na Web, para processar a verificação. É possível personalizar esse fluxo de algumas maneiras.

Localização dos e-mails de verificação

Para localizar os e-mails enviados pelo Identity Platform, defina o código do idioma antes de chamar verifyBeforeUpdateEmail():

Versão 8 para a Web

firebase.auth().languageCode = 'fr';

Versão 9 para a Web

import { getAuth } from "firebase/auth";

const auth = getAuth();
auth.languageCode = 'fr';

iOS

Auth.auth().languageCode = 'fr';

Android

FirebaseAuth.getInstance().setLanguageCode("fr");

Como passar um estado adicional

É possível usar as configurações de código de ação para incluir um estado adicional no e-mail de verificação ou processar a verificação em um app para dispositivos móveis. Por exemplo:

Versão 8 para a Web

var user = firebase.auth().currentUser;
var actionCodeSettings = {
  url: 'https://www.example.com/completeVerification?state=*****',
  iOS: {
    bundleId: 'com.example.ios'
  },
  android: {
    packageName: 'com.example.android',
    installApp: true,
    minimumVersion: '12'
  },
  handleCodeInApp: true,
  // When multiple custom dynamic link domains are defined, specify which
  // one to use.
  dynamicLinkDomain: "example.page.link"
};
user.verifyBeforeUpdateEmail(newEmail, actionCodeSettings).then(function() {
  // Email sent.
  // User must click the email link before the email is updated.
}).catch(function(error) {
  // An error happened.
});

Versão 9 para a Web

import { getAuth, verifyBeforeUpdateEmail } from "firebase/auth";

const auth = getAuth(firebaseApp);
const user = auth.currentUser
const actionCodeSettings = {
  url: 'https://www.example.com/completeVerification?state=*****',
  iOS: {
    bundleId: 'com.example.ios'
  },
  android: {
    packageName: 'com.example.android',
    installApp: true,
    minimumVersion: '12'
  },
  handleCodeInApp: true,
  // When multiple custom dynamic link domains are defined, specify which
  // one to use.
  dynamicLinkDomain: "example.page.link"
};

  verifyBeforeUpdateEmail(auth.currentUser, newEmail, actionCodeSettings).then(() => {
    // Email sent.
    // User must click the email link before the email is updated.
  }).catch((error) => {
    // An error happened.
  })

iOS

var actionCodeSettings = ActionCodeSettings.init()
actionCodeSettings.canHandleInApp = true
let user = Auth.auth().currentUser()
actionCodeSettings.URL =
    String(format: "https://www.example.com/?email=%@", user.email)
actionCodeSettings.iOSbundleID = Bundle.main.bundleIdentifier!
actionCodeSettings.setAndroidPakageName("com.example.android",
                                         installIfNotAvailable:true,
                                         minimumVersion:"12")
// When multiple custom dynamic link domains are defined, specify which one to use.
actionCodeSettings.dynamicLinkDomain = "example.page.link"
user.sendEmailVerification(withActionCodeSettings:actionCodeSettings { error in
  if error != nil {
    // Error occurred. Inspect error.code and handle error.
    return
  }
  // Email verification sent.
})
user.verifyBeforeUpdateEmail(newEmail, actionCodeSettings, completion: { (error) in
  if error != nil {
    // An error happened.
  }
  // Email sent.
  // User must click the email link before the email is updated.
})

Android

ActionCodeSettings actionCodeSettings =
    ActionCodeSettings.newBuilder()
       .setUrl("https://www.example.com/completeVerification?state=*****")
       .setHandleCodeInApp(true)
       .setAndroidPackageName(
         "com.example.android",
         /* installIfNotAvailable= */ true,
         /* minimumVersion= */ null)
       .setIOSBundleId("com.example.ios")
       // When multiple custom dynamic link domains are defined, specify
       // which one to use.
       .setDynamicLinkDomain("example.page.link")
       .build();
FirebaseUser multiFactorUser = FirebaseAuth.getInstance().getCurrentUser();
multiFactorUser
   .verifyBeforeUpdateEmail(newEmail, actionCodeSettings)
   .addOnCompleteListener(
      new OnCompleteListener<Void>() {
       @Override
       public void onComplete(@NonNull Task<Void> task) {
         if (task.isSuccessful()) {
          // Email sent.
          // User must click the email link before the email is updated.
         } else {
          // An error occurred.
         }
       }
      });

Como personalizar o gerenciador de verificação

Você pode criar seu próprio gerenciador para processar a verificação de e-mail. Veja no exemplo a seguir como verificar um código de ação e inspecionar os metadados antes de aplicá-lo:

Versão 8 para a Web

var email;
firebase.auth().checkActionCode(actionCode)
  .then(function(info) {
    // Operation is equal to
    // firebase.auth.ActionCodeInfo.Operation.VERIFY_AND_CHANGE_EMAIL
    var operation = info['operation'];
    // This is the old email.
    var previousEmail = info['data']['previousEmail'];
    // This is the new email the user is changing to.
    email = info['data']['email'];
    // TODO: Display a message to the end user that the email address of the account is
    // going to be changed from `fromEmail` to `email`
    // 
    // On confirmation.
    return firebase.auth().applyActionCode(actionCode)
  }).then(function() {
    // Confirm to the end user the email was updated.
    showUI('You can now sign in with your new email: ' + email);
  })
  .catch(function(error) {
    // Error occurred during confirmation. The code might have expired or the
    // link has been used before.
  });

Versão 9 para a Web

import { getAuth,  checkActionCode, applyActionCode} from "firebase/auth";

const auth = getAuth(firebaseApp);
var email;
checkActionCode(auth, actionCode)
  .then((info) => {
    // Operation is equal to
    // ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL
    const operation = info['operation'];
    // This is the old email.
    const previousEmail = info['data']['previousEmail'];
    // This is the new email the user is changing to.
    email = info['data']['email'];
    // TODO: Display a message to the end user that the email address of the account is
    // going to be changed from `fromEmail` to `email`
    // 
    // On confirmation.
    return applyActionCode(auth, actionCode)
  }).then(() => {
    // Confirm to the end user the email was updated.
    showUI('You can now sign in with your new email: ' + email);
  })
  .catch((error) => {
    // Error occurred during confirmation. The code might have expired or the
    // link has been used before.
  });

iOS

Auth.auth().checkActionCode(actionCode) { info, error in
  if error != nil {
    // Error occurred during confirmation. The code might have expired or the
    // link has been used before.
    return
  }
  // This is the new email the user is changing to.
  let email = info?.email
  // This is the old email.
  let oldEmail = info?.previousEmail
  // operation is equal to
  // firebase.auth.ActionCodeInfo.Operation.VERIFY_AND_CHANGE_EMAIL
  let operation = info?.operation
  // TODO: Display a message to the end user that the email address of the account is
  // going to be changed from `fromEmail` to `email`
  // 
  // On confirmation.
  return Auth.auth().applyActionCode(actionCode)
}

Android

FirebaseAuth.getInstance().checkActionCode(actionCode).addOnCompleteListener(
  new OnCompleteListener<ActionCodeResult>() {
    @Override
    public void onComplete(@NonNull Task<ActionCodeResult> task) {
      if (!task.isSuccessful()) {
        // Error occurred during confirmation. The code might have expired or the
        // link has been used before.
        return;
      }
      ActionCodeResult result = task.getResult();

      // This maps to VERIFY_AND_CHANGE_EMAIL.
      int operation = result.getOperation();

      if (operation == ActionCodeResult.VERIFY_AND_CHANGE_EMAIL) {
        ActionCodeEmailInfo actionCodeInfo =
            (ActionCodeEmailInfo) result.getInfo();

        String fromEmail = actionCodeInfo.getFromEmail();
        String email = actionCodeInfo.getEmail();
        // TODO: Display a message to the user that the email address
        // of the account is changing from `fromEmail` to `email` once
        // they confirm.
      }
    }
  });

Para saber mais, consulte o documento do Firebase sobre Como criar gerenciadores de ações de e-mail personalizados.

Como autenticar novamente um usuário

Mesmo que um usuário já esteja conectado, reautentique-o antes de executar operações confidenciais, como:

  • Alterar uma senha.
  • Adicionar ou remover um novo fator.
  • Atualizar informações pessoais (como um endereço).
  • Executar transações financeiras.
  • Excluir a conta de um usuário.

Para autenticar novamente um usuário com e-mail e senha:

Web

var resolver;
var credential = firebase.auth.EmailAuthProvider.credential(
    firebase.auth().currentUser.email, password);
firebase.auth().currentUser.reauthenticateWithCredential(credential)
  .then(function(userCredential) {
    // User successfully re-authenticated and does not require a second factor challenge.
    // ...
  })
  .catch(function(error) {
    if (error.code == 'auth/multi-factor-auth-required') {
      // Handle multi-factor authentication.
    } else {
      // Handle other errors.
    }
  });

iOS

let credential = EmailAuthProvider.credential(withEmail: email, password: password)
Auth.auth().currentUser.reauthenticate(with: credential, completion: { (result, error) in
  let authError = error as NSError?
  if (authError == nil || authError!.code != AuthErrorCode.secondFactorRequired.rawValue) {
    // User is not enrolled with a second factor or is successfully signed in.
  } else {
    // Handle multi-factor authentication.
  }
})

Android

FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
AuthCredential credential = EmailAuthProvider.getCredential(user.getEmail(), password);
user.reauthenticate(credential)
   .addOnCompleteListener(
      new OnCompleteListener<AuthResult>() {
       @Override
       public void onComplete(@NonNull Task<AuthResult> task) {
         if (task.isSuccessful()) {
          // User successfully re-authenticated and does not
          // require a second factor challenge.
          // ...
          return;
         }
         if (task.getException() instanceof FirebaseAuthMultiFactorException) {
           // Handle multi-factor authentication.
         } else {
          // Handle other errors.
         }
       }
      });

Para autenticar novamente usando um provedor OAuth, como a Microsoft:

Web

var resolver;
var user = firebase.auth().currentUser;
// Ask the user to re-authenticate with Microsoft.
var provider = new firebase.auth.OAuthProvider('microsoft.com');
// Microsoft provider allows the ability to provide a login_hint.
provider.setCustomParameters({
  login_hint: user.email
});
user.reauthenticateWithPopup(provider)
  .then(function(userCredential) {
    // User successfully re-authenticated and does not require a second factor challenge.
    // ...
  })
  .catch(function(error) {
    if (error.code == 'auth/multi-factor-auth-required') {
      // Handle multi-factor authentication.
    } else {
      // Unsupported second factor.
    } else {
      // Handle other errors.
    }
  });

iOS

var provider = OAuthProvider(providerID: "microsoft.com")
  // Replace nil with the custom class that conforms to AuthUIDelegate
  // you created in last step to use a customized web view.
  provider.getCredentialWith(nil) { credential, error in
    Auth.auth().currentUser.reauthenticate(with: credential, completion: { (result, error) in
      let authError = error as NSError?
      if (authError == nil || authError!.code != AuthErrorCode.secondFactorRequired.rawValue) {
        // User is not enrolled with a second factor or is successfully signed in.
        // ...
      } else {
        // Handle multi-factor authentication.
      }
    }
  })

Android

FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
OAuthProvider.Builder provider = OAuthProvider.newBuilder("microsoft.com");
provider.addCustomParameter("login_hint", user.getEmail());
user.startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
   .addOnCompleteListener(
      new OnCompleteListener<AuthResult>() {
       @Override
       public void onComplete(@NonNull Task<AuthResult> task) {
         if (task.isSuccessful()) {
          // User successfully re-authenticated and does not
          // require a second factor challenge.
          // ...
          return;
         }
         if (task.getException() instanceof FirebaseAuthMultiFactorException) {
           // Handle multi-factor authentication.
         } else {
          // Handle other errors such as wrong password.
         }
       }
      });

Revogar um segundo fator adicionado recentemente

Quando um usuário inscreve um segundo fator, o Identity Platform envia uma notificação para seu e-mail. Para proteger contra atividades não autorizadas, o e-mail inclui uma opção para reverter a adição de um segundo fator.

O Identity Platform fornece um modelo de e-mail e um gerenciador padrão, mas você também pode criar seu próprio. O exemplo a seguir mostra como criar um gerenciador personalizado:

Versão 8 para a Web

var obfuscatedPhoneNumber;
firebase.auth().checkActionCode(actionCode)
  .then(function(info) {
    // operation is equal to
    // firebase.auth.ActionCodeInfo.Operation.REVERT_SECOND_FACTOR_ADDITION
    var operation = info['operation'];
    // info.data.multiFactorInfo contains the data corresponding to the
    // enrolled second factor that the user is revoking.
    var multiFactorInfo = info['data']['multiFactorInfo'];
    obfuscatedPhoneNumber = multiFactorInfo['phoneNumber'];
    var displayName = multiFactorInfo['displayName'];
    // TODO: Display a message to the end user about the second factor that
    // was enrolled before the user can confirm the action to revert it.
    // ...
    // On confirmation.
    return firebase.auth().applyActionCode(actionCode)
  }).then(function() {
    // Confirm to the end user the phone number was removed from the account.
    showUI('The phone number ' + obfuscatedPhoneNumber +
         ' has been removed as a second factor from your account.' +
         ' You may also want to reset your password if you suspect' +
         ' your account was compromised.');
  })
  .catch(function(error) {
    // Error occurred during confirmation. The code might have expired or the
    // link has been used before.
  });

Versão 9 para a Web

const {
  getAuth,
  checkActionCode,
  applyActionCode
} = require("firebase/auth");

const auth = getAuth(firebaseApp);
var obfuscatedPhoneNumber;
checkActionCode(auth, actionCode)
  .then((info) => {
    // Operation is equal to
    // ActionCodeOperation.REVERT_SECOND_FACTOR_ADDITION
    const operation = info['operation'];
    // info.data.multiFactorInfo contains the data corresponding to the
    // enrolled second factor that the user is revoking.
    var multiFactorInfo = info['data']['multiFactorInfo'];
    obfuscatedPhoneNumber = multiFactorInfo['phoneNumber'];
    const displayName = multiFactorInfo['displayName'];
    // TODO: Display a message to the end user about the second factor that
    // was enrolled before the user can confirm the action to revert it.
    // ...
    // On confirmation.
    return applyActionCode(auth, actionCode)
  }).then(() => {
    // Confirm to the end user the phone number was removed from the account.
    showUI('The phone number ' + obfuscatedPhoneNumber +
         ' has been removed as a second factor from your account.' +
         ' You may also want to reset your password if you suspect' +
         ' your account was compromised.');
  })
  .catch((error) => {
    // Error occurred during confirmation. The code might have expired or the
    // link has been used before.
  });

iOS

Auth.auth().checkActionCode(actionCode) { info, error in
  if error != nil {
    // Error occurred during confirmation. The code might have expired or the
    // link has been used before.
    return
  }
  // This is the new email the user is changing to.
  let email = info?.email
  // This is the old email.
  let oldEmail = info?.previousEmail
  // operation is equal to
  // firebase.auth.ActionCodeInfo.Operation.REVERT_SECOND_FACTOR_ADDITION
  let operation = info?.operation
  // info.multiFactorInfo contains the data corresponding to the enrolled second
  // factor that the user is revoking.
  let multiFactorInfo = info?.multiFactorInfo
  let obfuscatedPhoneNumber = (multiFactorInfo as! PhoneMultiFactorInfo).phone
  // TODO: Display a message to the end user that the email address of the account is
  // going to be changed from `fromEmail` to `email`
  // 
  // On confirmation.
  return Auth.auth().applyActionCode(actionCode)
}

Android

FirebaseAuth.getInstance()
  .checkActionCode(actionCode)
  .continueWithTask(
      new Continuation<ActionCodeResult, Task<Void>>() {
        @Override
        public Task<Void> then(Task<ActionCodeResult> task) throws Exception {
          if (!task.isSuccessful()) {
            // Error occurred during confirmation. The code might have expired
            // or the link has been used before.
            return Tasks.forException(task.getException());
          }
          ActionCodeResult result = task.getResult();
          // The operation is equal to ActionCodeResult.REVERT_SECOND_FACTOR_ADDITION.
          int operation = result.getOperation();
          // The ActionCodeMultiFactorInfo contains the data corresponding to
          // the enrolled second factor that the user is revoking.
          ActionCodeMultiFactorInfo actionCodeInfo =
              (ActionCodeMultiFactorInfo) result.getInfo();
          PhoneMultiFactorInfo multiFactorInfo =
              (PhoneMultiFactorInfo) actionCodeInfo.getMultiFactorInfo();
          String obfuscatedPhoneNumber = multiFactorInfo.getPhoneNumber();
          String displayName = multiFactorInfo.getDisplayName();
          // We can now display a message to the end user about the second
          // factor that was enrolled before they confirm the action to revert
          // it.
          // ...
          // On user confirmation:
          return FirebaseAuth.getInstance().applyActionCode(actionCode);
        }
      })
  .addOnCompleteListener(
      new OnCompleteListener<Void>() {
        @Override
        public void onComplete(Task<Void> task) {
          if (task.isSuccessful()) {
            // Display a message to the user that the second factor
            // has been reverted.
          }
        }
      });

Para saber mais, consulte o documento do Firebase sobre Como criar gerenciadores de ações de e-mail personalizados.

Como recuperar um segundo fator

O Identity Platform não fornece um mecanismo integrado para recuperar segundos fatores. Se um usuário perder o acesso ao segundo fator, ele será bloqueado da conta. Para evitar que isso aconteça, considere o seguinte:

  • Avise os usuários que eles perderão o acesso à conta sem o segundo fator.
  • Incentivamos os usuários a registrar um fator secundário de backup.
  • Usando o SDK Admin para criar um fluxo de recuperação que desativa a autenticação multifator se o usuário puder verificar a identidade dele (por exemplo, fazendo upload de uma chave de recuperação ou respondendo perguntas pessoais).
  • Conceder à sua equipe de suporte a capacidade de gerenciar contas de usuário (incluindo a remoção de segundos de fatores) e oferecer uma opção para entrar em contato com eles, caso eles estejam bloqueados.

A redefinição de senha não permitirá que um usuário ignore a autenticação multifator. Se você redefinir a senha de um usuário usando sendPasswordResetEmail(), ele ainda precisará passar pelo desafio multifator ao fazer login com a nova senha.

Como cancelar a inscrição de um segundo fator

Para cancelar a inscrição de um segundo fator, acesse-o na lista de fatores inscritos do usuário e, em seguida, chame unenroll(). Como essa é uma operação confidencial, você precisará autenticar novamente o usuário primeiro se ele não tiver feito login recentemente.

Versão 8 para a Web

var options = user.multiFactor.enrolledFactors;
// Ask user to select from the enrolled options.
return user.multiFactor.unenroll(options[selectedIndex])
  .then(function() {
    // User successfully unenrolled selected factor.
  });

Versão 9 para a Web

const multiFactorUser = multiFactor(auth.currentUser);
const options = multiFactorUser.enrolledFactors
// Ask user to select from the enrolled options.
return multiFactorUser.unenroll(options[selectedIndex])
  .then(() =>
    // User successfully unenrolled selected factor.
  });

iOS

// Ask user to select from the enrolled options.
user?.multiFactor.unenroll(with: (user?.multiFactor.enrolledFactors[selectedIndex])!,
  completion: { (error) in
    // ...
})

Android

List<MultiFactorInfo> options = user.getMultiFactor().getEnrolledFactors();
// Ask user to select from the enrolled options.
user.getMultiFactor()
   .unenroll(options.get(selectedIndex))
   .addOnCompleteListener(
      new OnCompleteListener<Void>() {
       @Override
       public void onComplete(@NonNull Task<Void> task) {
         if (task.isSuccessful()) {
          // Successfully un-enrolled.
         }
       }
      });

Em alguns casos, o usuário pode ser desconectado depois de remover um segundo fator. Use onAuthStateChanged() para ouvir este caso e peça ao usuário para fazer login novamente.

A seguir