Bekerja bersama pengguna multi-faktor

Dokumen ini menunjukkan cara melakukan tugas umum dengan pengguna Identity Platform yang terdaftar dalam autentikasi multi-faktor.

Memperbarui email pengguna

Pengguna multi-faktor harus selalu memiliki alamat email terverifikasi. Tindakan ini mencegah pelaku kejahatan mendaftar ke aplikasi Anda dengan email yang bukan miliknya, lalu mengunci pemilik sebenarnya dari akunnya dengan menambahkan faktor kedua.

Untuk memperbarui email pengguna, gunakan metode verifyBeforeUpdateEmail(). Tidak seperti updateEmail(), metode ini mengharuskan pengguna untuk mengikuti link verifikasi sebelum Identity Platform memperbarui alamat email mereka. Contoh:

Web versi 8

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

Web versi 9

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

Secara default, Identity Platform mengirim email kepada pengguna, dan menyediakan pengendali berbasis web yang sederhana untuk memproses verifikasi. Ada beberapa cara untuk menyesuaikan alur ini.

Menerjemahkan email verifikasi

Untuk melokalkan email yang dikirim oleh Identity Platform, tetapkan kode bahasa sebelum memanggil verifyBeforeUpdateEmail():

Web versi 8

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

Web versi 9

import { getAuth } from "firebase/auth";

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

iOS

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

Android

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

Meneruskan status tambahan

Anda dapat menggunakan setelan kode tindakan untuk menyertakan status tambahan dalam email verifikasi, atau menangani verifikasi dari aplikasi seluler. Misalnya:

Web versi 8

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

Web versi 9

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

Menyesuaikan pengendali verifikasi

Anda dapat membuat pengendali sendiri untuk memproses verifikasi email. Contoh berikut menunjukkan cara memeriksa kode tindakan dan memeriksa metadatanya sebelum menerapkan:

Web versi 8

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

Web versi 9

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

Untuk mempelajari lebih lanjut, lihat dokumentasi Firebase tentang Membuat pengendali tindakan email kustom.

Mengautentikasi ulang pengguna

Meskipun pengguna sudah login, Anda mungkin ingin mengautentikasi ulang mereka sebelum melakukan operasi sensitif, seperti:

  • Mengubah sandi.
  • Menambahkan atau menghapus faktor kedua baru.
  • Memperbarui informasi pribadi (seperti alamat).
  • Menjalankan transaksi keuangan.
  • Menghapus akun pengguna.

Untuk mengautentikasi ulang pengguna dengan email dan sandi:

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

Untuk mengautentikasi ulang menggunakan penyedia OAuth, seperti 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.
         }
       }
      });

Membatalkan faktor kedua yang baru ditambahkan

Saat pengguna mendaftarkan faktor kedua, Identity Platform akan mengirimkan notifikasi ke email mereka. Untuk melindungi dari aktivitas yang tidak sah, email menyertakan opsi untuk membatalkan penambahan faktor kedua.

Identity Platform menyediakan template dan pengendali email default, tetapi Anda juga dapat membuat template sendiri. Contoh berikut menunjukkan cara membuat pengendali kustom:

Web versi 8

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

Web versi 9

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

Untuk mempelajari lebih lanjut, lihat dokumentasi Firebase tentang Membuat pengendali tindakan email kustom.

Memulihkan faktor kedua

Identity Platform tidak menyediakan mekanisme bawaan untuk memulihkan faktor kedua. Jika pengguna kehilangan akses ke faktor kedua, mereka tidak akan dapat mengakses akunnya. Untuk mencegah hal ini terjadi, pertimbangkan hal berikut:

  • Memberi peringatan kepada pengguna bahwa mereka akan kehilangan akses ke akun mereka tanpa faktor kedua.
  • Sangat mendorong pengguna untuk mendaftarkan faktor sekunder cadangan.
  • Menggunakan Admin SDK untuk membuat alur pemulihan yang menonaktifkan autentikasi multi-faktor jika pengguna dapat memverifikasi identitasnya dengan memadai (misalnya, dengan mengupload kunci pemulihan atau menjawab pertanyaan pribadi).
  • Memberikan kemampuan kepada tim dukungan Anda untuk mengelola akun pengguna (termasuk menghapus faktor kedua), dan memberikan opsi bagi pengguna untuk menghubungi mereka jika mereka terkunci dari akun mereka.

Mereset sandi tidak akan memungkinkan pengguna mengabaikan autentikasi multi-faktor. Jika Anda mereset sandi pengguna menggunakan sendPasswordResetEmail(), mereka akan tetap diwajibkan untuk lulus verifikasi multi-faktor saat login dengan sandi baru mereka.

Membatalkan pendaftaran faktor kedua

Untuk membatalkan pendaftaran faktor kedua, dapatkan dari daftar faktor yang terdaftar pengguna, lalu panggil unenroll(). Karena ini adalah operasi sensitif, Anda harus mengautentikasi ulang pengguna terlebih dahulu jika mereka belum login baru-baru ini.

Web versi 8

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

Web versi 9

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

Dalam beberapa kasus, pengguna mungkin logout setelah menghapus faktor kedua. Gunakan onAuthStateChanged() untuk memproses kasus ini, dan minta pengguna untuk login lagi.

Langkah selanjutnya