הוספת אימות רב-שלבי לאפליקציה ל-iOS

אם שדרגת ל-Firebase Authentication with Identity Platform, יש לך אפשרות להוסיף אימות רב-שלבי באמצעות SMS לאפליקציה ל-iOS.

אימות רב-שלבי מגביר את האבטחה של האפליקציה. לרוב, תוקפים מצליחים לפרוץ לסיסמאות ולחשבונות ברשתות החברתיות, אבל קשה יותר להם ליירט הודעת טקסט.

לפני שמתחילים

  1. להפעיל לפחות ספק אחד שתומך באימות רב-שלבי. כל הספקים תומכים באימות דו-שלבי, למעט אימות בטלפון, אימות אנונימי ו-Apple Game Center.

  2. מוודאים שהאפליקציה מאמתת את כתובות האימייל של המשתמשים. MFA מחייב אימות אימייל. זה מונע מגורמים זדוניים להירשם לשירות באמצעות אימייל שלא בבעלותם, ואז נועלים את הבעלים האמיתי על ידי הוספת נכס בשקלול.

הפעלת אימות רב-גורמי

  1. פותחים את אימות > שיטת הכניסה במסוף Firebase.

  2. בקטע מתקדם, מפעילים את האפשרות אימות רב-שלבי ב-SMS.

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

  3. אם עדיין לא אישרת את הדומיין של האפליקציה, צריך להוסיף אותו להרשאה ברשימה אימות > הגדרות במסוף Firebase.

אימות האפליקציה

Firebase צריך לאמת שבקשות SMS מגיעות מאת אפליקציה. אפשר לעשות זאת בשתי דרכים:

  • התראות APNs שקשות: כשמשתמשים נכנסים לחשבון בפעם הראשונה, Firebase יכול לשלוח התראת דחיפה שקטה למכשיר שלהם. האימות יכול להמשיך אם האפליקציה מקבלת את ההתראה. שימו לב: החל מגרסה 8.0 של iOS, לא צריך לבקש מהמשתמש לאפשר התראות לדחוף כדי להשתמש בשיטה הזו.

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

שימוש בהתראות שקטות

כדי להפעיל התראות APN לשימוש עם Firebase:

  1. ב-Xcode, מפעילים את התראות ה-push בפרויקט.

  2. מעלים את מפתח האימות של APNs באמצעות מסוף Firebase (השינויים יועברו אוטומטית אל Google Cloud Firebase). אם עדיין אין לכם מפתח אימות של APNs, תוכלו לקרוא את המאמר הגדרת APNs באמצעות FCM כדי ללמוד איך לקבל אותו.

    1. פותחים את מסוף Firebase.

    2. עוברים אל Project Settings (הגדרות הפרויקט).

    3. בוחרים את הכרטיסייה העברת הודעות בענן.

    4. בקטע APNs authentication key בקטע iOS app configuration, לוחצים על Upload.

    5. בוחרים את המפתח.

    6. מוסיפים את מזהה המפתח. מזהה המפתח מופיע בקטע Certificates, Identifiers & Profiles בApple Developer Member Center.

    7. לוחצים על Upload (העלאה).

אם כבר יש לכם אישור APNs, תוכלו להעלות את האישור במקום זאת.

שימוש באימות מסוג reCAPTCHA

כדי לאפשר ל-Client SDK להשתמש ב-reCAPTCHA:

  1. פותחים את הגדרות הפרויקט ב-Xcode.

  2. לוחצים לחיצה כפולה על שם הפרויקט בתצוגת העץ השמאלית.

  3. בוחרים את האפליקציה בקטע יעדים.

  4. לוחצים על הכרטיסייה מידע.

  5. מרחיבים את הקטע URL Types.

  6. לוחצים על הלחצן +.

  7. מזינים את מזהה הלקוח ההפוך בשדה סכימות של כתובות URL. הערך הזה מופיע בקובץ התצורה GoogleService-Info.plist בתור REVERSED_CLIENT_ID.

בסיום התהליך, ההגדרות האישיות שלכם אמורות להיראות כך:

סכימות בהתאמה אישית

אפשר גם להתאים אישית את האופן שבו האפליקציה מציגה את SFSafariViewController או את UIWebView כשהיא מציגה את reCAPTCHA. לבצע היא ליצור מחלקה מותאמת אישית שתואמת לפרוטוקול FIRAuthUIDelegate, ומעבירים אותו אל verifyPhoneNumber:UIDelegate:completion:.

בחירת דפוס הרישום

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

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

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

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

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

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

כדי לרשום גורם משני חדש עבור משתמש:

  1. מאמתים מחדש את המשתמש.

  2. מבקשים מהמשתמש להזין את מספר הטלפון שלו.

  3. יצירת סשן רב-שלבי למשתמש:

    Swift

    authResult.user.multiFactor.getSessionWithCompletion() { (session, error) in
      // ...
    }
    

    Objective-C

    [authResult.user.multiFactor
      getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session,
                                NSError * _Nullable error) {
        // ...
    }];
    
  4. שולחים הודעת אימות לטלפון של המשתמש. יש לוודא שמספר הטלפון בפורמט + מוביל ללא סימני פיסוק או רווחים לבנים אחרים (עבור דוגמה: +15105551234)

    Swift

    // Send SMS verification code.
    PhoneAuthProvider.provider().verifyPhoneNumber(
      phoneNumber,
      uiDelegate: nil,
      multiFactorSession: session) { (verificationId, error) in
        // verificationId will be needed for enrollment completion.
    }
    

    Objective-C

    // Send SMS verification code.
    [FIRPhoneAuthProvider.provider verifyPhoneNumber:phoneNumber
                                          UIDelegate:nil
                                  multiFactorSession:session
                                          completion:^(NSString * _Nullable verificationID,
                                                        NSError * _Nullable error) {
        // verificationId will be needed for enrollment completion.
    }];
    

    אמנם זו לא חובה, אבל מומלץ להודיע למשתמשים מראש על כך הם יקבלו הודעת SMS, ויחולו התעריפים הרגילים.

    השיטה verifyPhoneNumber() מתחילה את תהליך אימות האפליקציות בשלב ברקע באמצעות התראה שקטה. אם התראה שקטה מופעלת לא זמין,נוצר במקום זאת אתגר reCAPTCHA.

  5. לאחר שליחת קוד ה-SMS, בקשו מהמשתמש לאמת את הקוד. לאחר מכן, תוכלו להשתמש בתשובה שלהם כדי ליצור PhoneAuthCredential:

    Swift

    // Ask user for the verification code. Then:
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId,
      verificationCode: verificationCode)
    

    Objective-C

    // Ask user for the SMS verification code. Then:
    FIRPhoneAuthCredential *credential = [FIRPhoneAuthProvider.provider
                                           credentialWithVerificationID:verificationID
                                           verificationCode:kPhoneSecondFactorVerificationCode];
    
  6. מאתחלים אובייקט טענת נכוֹנוּת (assertion):

    Swift

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    Objective-C

    FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  7. משלימים את ההרשמה. אפשר גם לציין שם מוצג לגורם השני. זה שימושי למשתמשים שיש להם הרבה גורמים שמשפיעים על השנייה, במקרה של אנונימיזציה של מספר הטלפון בתהליך האימות (במקרה של לדוגמה, +1******1234).

    Swift

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    user.multiFactor.enroll(with: assertion, displayName: displayName) { (error) in
      // ...
    }
    

    Objective-C

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    [authResult.user.multiFactor enrollWithAssertion:assertion
                                         displayName:nil
                                          completion:^(NSError * _Nullable error) {
        // ...
    }];
    

הקוד הבא מציג דוגמה מלאה לרישום של גורם שני:

Swift

let user = Auth.auth().currentUser
user?.multiFactor.getSessionWithCompletion({ (session, error) in
  // Send SMS verification code.
  PhoneAuthProvider.provider().verifyPhoneNumber(
    phoneNumber,
    uiDelegate: nil,
    multiFactorSession: session
  ) { (verificationId, error) in
    // verificationId will be needed for enrollment completion.
    // Ask user for the verification code.
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId!,
      verificationCode: phoneSecondFactorVerificationCode)
    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    user?.multiFactor.enroll(with: assertion, displayName: displayName) { (error) in
      // ...
    }
  }
})

Objective-C

FIRUser *user = FIRAuth.auth.currentUser;
[user.multiFactor getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session,
                                              NSError * _Nullable error) {
    // Send SMS verification code.
    [FIRPhoneAuthProvider.provider
      verifyPhoneNumber:phoneNumber
      UIDelegate:nil
      multiFactorSession:session
      completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
        // verificationId will be needed for enrollment completion.

        // Ask user for the verification code.
        // ...

        // Then:
        FIRPhoneAuthCredential *credential =
            [FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationID
                                                        verificationCode:kPhoneSecondFactorVerificationCode];
        FIRMultiFactorAssertion *assertion =
            [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];

        // Complete enrollment. This will update the underlying tokens
        // and trigger ID token change listener.
        [user.multiFactor enrollWithAssertion:assertion
                                  displayName:displayName
                                    completion:^(NSError * _Nullable error) {
            // ...
        }];
    }];
}];

כל הכבוד! רשמתם בהצלחה גורם אימות שני למשתמש.

הכנסת משתמשים באמצעות שלב שני

כדי להיכנס משתמש באמצעות אימות דו-שלבי באמצעות SMS:

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

    לדוגמה, אם הגורם הראשון של המשתמש היה כתובת אימייל וסיסמה:

    Swift

    Auth.auth().signIn(
      withEmail: email,
      password: password
    ) { (result, error) in
      let authError = error as NSError
      if authError?.code == AuthErrorCode.secondFactorRequired.rawValue {
        // The user is a multi-factor user. Second factor challenge is required.
        let resolver =
          authError!.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver
        // ...
      } else {
        // Handle other errors such as wrong password.
      }
    }
    

    Objective-C

    [FIRAuth.auth signInWithEmail:email
                         password:password
                       completion:^(FIRAuthDataResult * _Nullable authResult,
                                    NSError * _Nullable error) {
        if (error == nil || error.code != FIRAuthErrorCodeSecondFactorRequired) {
            // User is not enrolled with a second factor and is successfully signed in.
            // ...
        } else {
            // The user is a multi-factor user. Second factor challenge is required.
        }
    }];
    

    אם הגורם הראשון של המשתמש הוא ספק מאוחד, כמו OAuth, צריך לתפוס את השגיאה אחרי הקריאה ל-getCredentialWith().

  2. אם למשתמש יש כמה גורמים משניים רשומים, צריך לשאול אותו באיזה מהם להשתמש. אפשר לראות את מספר הטלפון מוסתר עם resolver.hints[selectedIndex].phoneNumber והשם המוצג עם resolver.hints[selectedIndex].displayName.

    Swift

    // Ask user which second factor to use. Then:
    if resolver.hints[selectedIndex].factorID == PhoneMultiFactorID {
      // User selected a phone second factor.
      // ...
    } else if resolver.hints[selectedIndex].factorID == TotpMultiFactorID {
      // User selected a TOTP second factor.
      // ...
    } else {
      // Unsupported second factor.
    }
    

    Objective-C

    FIRMultiFactorResolver *resolver =
        (FIRMultiFactorResolver *) error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];
    
    // Ask user which second factor to use. Then:
    FIRPhoneMultiFactorInfo *hint = (FIRPhoneMultiFactorInfo *) resolver.hints[selectedIndex];
    if (hint.factorID == FIRPhoneMultiFactorID) {
      // User selected a phone second factor.
      // ...
    } else if (hint.factorID == FIRTOTPMultiFactorID) {
      // User selected a TOTP second factor.
      // ...
    } else {
      // Unsupported second factor.
    }
    
  3. שליחת הודעת אימות לטלפון של המשתמש:

    Swift

    // Send SMS verification code.
    let hint = resolver.hints[selectedIndex] as! PhoneMultiFactorInfo
    PhoneAuthProvider.provider().verifyPhoneNumber(
      with: hint,
      uiDelegate: nil,
      multiFactorSession: resolver.session
    ) { (verificationId, error) in
      // verificationId will be needed for sign-in completion.
    }
    

    Objective-C

    // Send SMS verification code
    [FIRPhoneAuthProvider.provider
      verifyPhoneNumberWithMultiFactorInfo:hint
      UIDelegate:nil
      multiFactorSession:resolver.session
      completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
        if (error != nil) {
            // Failed to verify phone number.
        }
    }];
    
  4. אחרי שליחת קוד ה-SMS, מבקשים מהמשתמש לאמת את הקוד ולהשתמש בו כדי ליצור PhoneAuthCredential:

    Swift

    // Ask user for the verification code. Then:
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId!,
      verificationCode: verificationCodeFromUser)
    

    Objective-C

    // Ask user for the SMS verification code. Then:
    FIRPhoneAuthCredential *credential =
        [FIRPhoneAuthProvider.provider
          credentialWithVerificationID:verificationID
                      verificationCode:verificationCodeFromUser];
    
  5. מאתחלים אובייקט של טענת נכוֹנוּת עם פרטי הכניסה:

    Swift

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    Objective-C

    FIRMultiFactorAssertion *assertion =
        [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  6. פותרים את הבעיה בכניסה. לאחר מכן תוכלו לגשת לתוצאת הכניסה המקורית, שכוללת את הנתונים הרגילים הספציפיים לספק ופרטי הכניסה לאימות:

    Swift

    // Complete sign-in. This will also trigger the Auth state listeners.
    resolver.resolveSignIn(with: assertion) { (authResult, error) in
      // authResult will also contain the user, additionalUserInfo, optional
      // credential (null for email/password) associated with the first factor sign-in.
    
      // For example, if the user signed in with Google as a first factor,
      // authResult.additionalUserInfo will contain data related to Google provider that
      // the user signed in with.
    
      // user.credential contains the Google OAuth credential.
      // user.credential.accessToken contains the Google OAuth access token.
      // user.credential.idToken contains the Google OAuth ID token.
    }
    

    Objective-C

    // Complete sign-in.
    [resolver resolveSignInWithAssertion:assertion
                              completion:^(FIRAuthDataResult * _Nullable authResult,
                                            NSError * _Nullable error) {
        if (error != nil) {
            // User successfully signed in with the second factor phone number.
        }
    }];
    

הקוד הבא מציג דוגמה מלאה לכניסה של משתמש עם אימות רב-גורמי:

Swift

Auth.auth().signIn(
  withEmail: email,
  password: password
) { (result, error) in
  let authError = error as NSError?
  if authError?.code == AuthErrorCode.secondFactorRequired.rawValue {
    let resolver =
      authError!.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver

    // Ask user which second factor to use.
    // ...

    // Then:
    let hint = resolver.hints[selectedIndex] as! PhoneMultiFactorInfo

    // Send SMS verification code
    PhoneAuthProvider.provider().verifyPhoneNumber(
      with: hint,
      uiDelegate: nil,
      multiFactorSession: resolver.session
    ) { (verificationId, error) in
      if error != nil {
        // Failed to verify phone number.
      }
      // Ask user for the SMS verification code.
      // ...

      // Then:
      let credential = PhoneAuthProvider.provider().credential(
        withVerificationID: verificationId!,
        verificationCode: verificationCodeFromUser)
      let assertion = PhoneMultiFactorGenerator.assertion(with: credential)

      // Complete sign-in.
      resolver.resolveSignIn(with: assertion) { (authResult, error) in
        if error != nil {
          // User successfully signed in with the second factor phone number.
        }
      }
    }
  }
}

Objective-C

[FIRAuth.auth signInWithEmail:email
                     password:password
                   completion:^(FIRAuthDataResult * _Nullable authResult,
                               NSError * _Nullable error) {
    if (error == nil || error.code != FIRAuthErrorCodeSecondFactorRequired) {
        // User is not enrolled with a second factor and is successfully signed in.
        // ...
    } else {
        FIRMultiFactorResolver *resolver =
            (FIRMultiFactorResolver *) error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];

        // Ask user which second factor to use.
        // ...

        // Then:
        FIRPhoneMultiFactorInfo *hint = (FIRPhoneMultiFactorInfo *) resolver.hints[selectedIndex];

        // Send SMS verification code
        [FIRPhoneAuthProvider.provider
          verifyPhoneNumberWithMultiFactorInfo:hint
                                    UIDelegate:nil
                            multiFactorSession:resolver.session
                                    completion:^(NSString * _Nullable verificationID,
                                                NSError * _Nullable error) {
            if (error != nil) {
                // Failed to verify phone number.
            }

            // Ask user for the SMS verification code.
            // ...

            // Then:
            FIRPhoneAuthCredential *credential =
                [FIRPhoneAuthProvider.provider
                  credentialWithVerificationID:verificationID
                              verificationCode:kPhoneSecondFactorVerificationCode];
            FIRMultiFactorAssertion *assertion =
                [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];

            // Complete sign-in.
            [resolver resolveSignInWithAssertion:assertion
                                      completion:^(FIRAuthDataResult * _Nullable authResult,
                                                    NSError * _Nullable error) {
                if (error != nil) {
                    // User successfully signed in with the second factor phone number.
                }
            }];
        }];
    }
}];

כל הכבוד! התחברתם בהצלחה למשתמש באמצעות אימות רב-גורמי.

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