Attività con utenti con autenticazione a più fattori

Questo documento mostra come eseguire le attività comuni con gli utenti Identity Platform registrati per l'autenticazione a più fattori.

Aggiornamento dell'email di un utente

Gli utenti che utilizzano l'autenticazione a più fattori devono sempre avere un indirizzo email verificato. In questo modo gli utenti malintenzionati non possono registrarsi alla tua app con un indirizzo email di cui non sono proprietari e quindi bloccare il reale proprietario aggiungendo un secondo fattore.

Per aggiornare l'indirizzo email di un utente, utilizza il metodo verifyBeforeUpdateEmail(). A differenza di updateEmail(), questo metodo richiede che l'utente segua un link di verifica prima che Identity Platform aggiorni il proprio indirizzo email. Ad esempio:

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

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

Per impostazione predefinita, Identity Platform invia all'utente un'email e fornisce un semplice gestore basato sul web per l'elaborazione della verifica. Ci sono diversi modi per personalizzare questo flusso.

Localizzazione delle email di verifica

Per localizzare le email inviate da Identity Platform, imposta il codice lingua prima di chiamare verifyBeforeUpdateEmail():

Versione 8 web

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

Versione 9 web

import { getAuth } from "firebase/auth";

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

iOS

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

Android

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

Passaggio di uno stato aggiuntivo in corso...

Puoi utilizzare le impostazioni del codice di azione per includere uno stato aggiuntivo nell'email di verifica o gestire la verifica da un'app mobile. Ad esempio:

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

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

Personalizzazione del gestore della verifica

Puoi creare un tuo gestore per elaborare la verifica email. L'esempio seguente mostra come controllare un codice di azione e i relativi metadati prima di applicarlo:

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

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

Per saperne di più, consulta la documentazione di Firebase sulla creazione di gestori di azioni email personalizzati.

Riautenticazione di un utente

Anche se un utente ha già eseguito l'accesso, è consigliabile autenticarlo nuovamente prima di eseguire operazioni sensibili, ad esempio:

  • Modifica di una password.
  • Aggiunta o rimozione di un nuovo secondo fattore.
  • Aggiornare le informazioni personali (ad esempio un indirizzo).
  • Esecuzione di transazioni finanziarie.
  • Eliminazione dell'account di un utente.

Per autenticare nuovamente un utente con un indirizzo email e una password:

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

Per eseguire nuovamente l'autenticazione utilizzando un provider OAuth, ad esempio 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.
         }
       }
      });

Revoca di un secondo fattore aggiunto di recente

Quando un utente registra un secondo fattore, Identity Platform invia una notifica al suo indirizzo email. Per proteggerti da attività non autorizzate, l'email include un'opzione per annullare l'aggiunta di un secondo fattore.

Identity Platform fornisce un modello e un gestore email predefiniti, ma puoi anche crearne uno personalizzato. L'esempio seguente mostra come creare un gestore personalizzato:

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

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

Per saperne di più, consulta la documentazione di Firebase sulla creazione di gestori di azioni email personalizzati.

Recuperare un secondo fattore

Identity Platform non fornisce un meccanismo integrato per il recupero di secondi fattori. Se un utente perde l'accesso al secondo fattore, non potrà più accedere al proprio account. Per evitare che ciò accada, tieni presente quanto segue:

  • Avvisare gli utenti che perderanno l'accesso al proprio account senza il secondo fattore.
  • Incoraggiano vivamente gli utenti a registrare un fattore secondario di backup.
  • Utilizzare SDK Admin per creare un flusso di recupero che disabilita l'autenticazione a più fattori se l'utente può verificare in modo sufficiente la propria identità (ad esempio caricando una chiave di recupero o rispondendo a domande personali).
  • Concedere al team di assistenza la possibilità di gestire gli account utente (compresa la rimozione di secondi fattori) e offrire agli utenti la possibilità di contattarli se non riescono ad accedere al proprio account.

La reimpostazione della password non consente all'utente di ignorare l'autenticazione basata su più fattori. Se reimposti la password di un utente utilizzando sendPasswordResetEmail(), sarà comunque necessario superare la verifica dell'autenticazione a più fattori dopo aver eseguito l'accesso con la nuova password.

Annullare la registrazione di un secondo fattore

Per annullare la registrazione di un secondo fattore, recuperalo dall'elenco dei fattori registrati dell'utente, quindi chiama unenroll(). Poiché si tratta di un'operazione sensibile, dovrai eseguire nuovamente l'autenticazione dell'utente se non ha eseguito l'accesso di recente.

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

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

In alcuni casi, l'utente potrebbe essere disconnesso dopo aver rimosso un secondo fattore. Utilizza onAuthStateChanged() per ascoltare questa richiesta e chiedi all'utente di accedere di nuovo.

Passaggi successivi