עבודה עם משתמשים שמוגדרת להם אימות רב-שלבי

במאמר הזה מוסבר איך לבצע משימות נפוצות עם משתמשי Identity Platform שנרשמו לאימות רב-שלבי.

עדכון כתובת האימייל של משתמש

למשתמשים עם אימות דו-שלבי תמיד צריכה להיות כתובת אימייל מאומתת. כך נמנע מצבים שבהם גורמים זדוניים נרשמים לאפליקציה באמצעות כתובת אימייל שלא בבעלותם, ואז נועלים את הבעלים האמיתי של הכתובת על ידי הוספת אמצעי אימות נוסף.

כדי לעדכן את כתובת האימייל של משתמש, משתמשים בשיטה verifyBeforeUpdateEmail(). בניגוד לשיטה של updateEmail(), בשיטה הזו המשתמש צריך ללחוץ על קישור לאימות לפני שכתובת האימייל שלו מתעדכנת ב-Identity Platform. לדוגמה:

גרסת אינטרנט 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.
});

גרסה 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.
         }
       }
      });

כברירת מחדל, מערכת Identity Platform שולחת למשתמש אימייל ומספקת אמצעי פשוט מבוסס-אינטרנט לטיפול באימות. יש כמה דרכים להתאים אישית את התהליך הזה.

התאמה לשפה המקומית של הודעות האימות

כדי להתאים לשפה מסוימת את האימיילים שנשלחים על ידי Identity Platform, צריך להגדיר את קוד השפה לפני שקוראים ל-verifyBeforeUpdateEmail():

גרסת אינטרנט 8

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

גרסה 9 לאינטרנט

import { getAuth } from "firebase/auth";

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

iOS

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

Android

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

העברת מצב נוסף

אתם יכולים להשתמש בהגדרות של קוד הפעולה כדי לכלול מצב נוסף באימייל האימות, או כדי לטפל באימות מאפליקציה לנייד. לדוגמה:

גרסת אינטרנט 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.
});

גרסה 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.
         }
       }
      });

התאמה אישית של רכיב ה-handler של האימות

אתם יכולים ליצור בעצמכם אמצעי טיפול כדי לעבד אימות של כתובות אימייל. בדוגמה הבאה אפשר לראות איך בודקים קוד פעולה ובוחנים את המטא-נתונים שלו לפני שמחילים אותו:

גרסת אינטרנט 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.
  });

גרסה 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.
      }
    }
  });

מידע נוסף זמין במסמכי התיעוד של Firebase בנושא יצירת אמצעי טיפול מותאמים אישית בפעולות באימייל.

אימות מחדש של משתמש

גם אם משתמש כבר מחובר, יכול להיות שתרצו לבצע אימות מחדש לפני ביצוע פעולות רגישות, כמו:

  • שינוי סיסמה.
  • הוספה או הסרה של גורם אימות חדש.
  • עדכון מידע אישי (כמו כתובת).
  • ביצוע עסקאות פיננסיות.
  • מחיקת חשבון של משתמש.

כדי לבצע אימות מחדש של משתמש באמצעות אימייל וסיסמה:

אינטרנט

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

כדי לבצע אימות מחדש באמצעות ספק OAuth, כמו מיקרוסופט:

אינטרנט

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

ביטול של גורם שני שנוסף לאחרונה

כשמשתמש נרשם לגורם אימות נוסף, מערכת Identity Platform שולחת התראה לאימייל שלו. כדי להגן מפני פעילות לא מורשית, האימייל כולל אפשרות לביטול ההוספה של אמצעי אימות נוסף.

ב-Identity Platform יש תבנית ומטפל ברירת מחדל לאימייל, אבל אתם יכולים גם ליצור משלכם. בדוגמה הבאה אפשר לראות איך יוצרים handler בהתאמה אישית:

גרסת אינטרנט 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.
  });

גרסה 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.
          }
        }
      });

מידע נוסף זמין במסמכי התיעוד של Firebase בנושא יצירת אמצעי טיפול מותאמים אישית בפעולות באימייל.

שחזור של גורם שני

ב-Identity Platform אין מנגנון מובנה לשחזור של אמצעי אימות שניים. אם משתמש מאבד את הגישה לגורם השני, הגישה לחשבון שלו תיחסם. כדי למנוע את המצב הזה, כדאי:

  • אזהרה למשתמשים שהם יאבדו את הגישה לחשבון שלהם אם לא יהיה להם את הגורם השני.
  • מומלץ מאוד למשתמשים לרשום אמצעי גיבוי לאימות דו-שלבי.
  • שימוש ב-SDK לאדמינים כדי ליצור תהליך שחזור שמשבית את האימות הרב-שלבי אם המשתמש יכול לאמת את הזהות שלו בצורה מספקת (לדוגמה, על ידי העלאת מפתח שחזור או מענה על שאלות אישיות).
  • לתת לצוות התמיכה שלכם את האפשרות לנהל חשבונות משתמשים (כולל הסרת אמצעי אימות נוספים), ולספק למשתמשים אפשרות ליצור קשר עם הצוות אם הם לא מצליחים להיכנס לחשבון שלהם.

איפוס הסיסמה לא מאפשר למשתמש לעקוף את האימות הרב-שלבי. אם תאפסו סיסמה של משתמש באמצעות sendPasswordResetEmail(), הוא עדיין יידרש לעבור את האתגר של האימות הרב-שלבי כשינסה להיכנס לחשבון עם הסיסמה החדשה.

ביטול ההרשמה לגורם השני

כדי לבטל את ההרשמה של גורם אימות נוסף, מקבלים אותו מרשימת הגורמים הרשומים של המשתמש ואז קוראים ל-unenroll(). מכיוון שמדובר בפעולה רגישה, אם המשתמש לא נכנס לחשבון לאחרונה, צריך לאמת אותו מחדש.

גרסת אינטרנט 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.
  });

גרסה 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.
         }
       }
      });

במקרים מסוימים, יכול להיות שהמשתמש יצא מהחשבון אחרי הסרת האימות הדו-שלבי. משתמשים ב-onAuthStateChanged() כדי להאזין למקרה הזה, ומבקשים מהמשתמש להיכנס שוב.

המאמרים הבאים