將多重身份驗證新增至您的 iOS 應用程式

如果您已升級至使用 Identity Platform 進行 Firebase 驗證,則可以將 SMS 多重驗證新增至您的 iOS 應用程式。

多重身份驗證可提高應用程式的安全性。雖然攻擊者經常破壞密碼和社交帳戶,但攔截簡訊卻更加困難。

在你開始之前

  1. 啟用至少一個支援多重身份驗證的提供者。每個提供者都支援 MFA,電話身份驗證、匿名身份驗證和 Apple Game Center除外

  2. 確保您的應用程式正在驗證使用者電子郵件。 MFA 需要電子郵件驗證。這可以防止惡意行為者使用不屬於他們的電子郵件註冊服務,然後透過添加第二個因素來鎖定真正的所有者。

啟用多重身份驗證

  1. 開啟 Firebase 控制台的驗證 > 登入方法頁面。

  2. 「進階」部分中,啟用簡訊多重身份驗證

    您還應該輸入用於測試應用程式的電話號碼。雖然可選,但強烈建議註冊測試電話號碼,以避免開發過程中受到限制。

  3. 如果您尚未授權套用的網域,請將其新增至 Firebase 控制台的「驗證」>「設定」頁面上的允許清單中。

驗證您的應用程式

Firebase 需要驗證簡訊請求是否來自您的應用程式。您可以透過兩種方式執行此操作:

  • 無聲 APN 通知:當您首次登入使用者時,Firebase 可以向使用者的裝置發送無聲推播通知。如果應用程式收到通知,則可以繼續進行身份驗證。請注意,從 iOS 8.0 開始,您不需要請求使用者允許推播通知即可使用此方法。

  • reCAPTCHA 驗證:如果您無法傳送靜默通知(例如,因為使用者停用了背景刷新,或者您正在 iOS 模擬器中測試您的應用程式),則可以使用 reCAPTCHA。在許多情況下,reCAPTCHA 會自動解決,無需使用者互動。

使用無聲通知

若要啟用 APNs 通知以與 Firebase 一起使用:

  1. 在 Xcode 中,為您的專案啟用推播通知

  2. 使用 Firebase 控制台上傳您的 APNs 驗證金鑰(您的變更將自動轉移到 Google Cloud Firebase)。如果您還沒有 APNs 驗證金鑰,請參閱使用 FCM 設定 APNs以了解如何取得它。

    1. 開啟Firebase 控制台

    2. 導航至項目設定

    3. 選擇雲端訊息傳遞選項卡。

    4. APNs 驗證金鑰下的iOS 應用程式設定部分中,按一下上傳

    5. 選擇您的密鑰。

    6. 新增密鑰的密鑰 ID。您可以在Apple 開發者會員中心憑證、識別碼和設定檔下找到密鑰 ID。

    7. 點擊上傳

如果您已有 APNs 證書,則可以上傳該證書。

使用 reCAPTCHA 驗證

要讓客戶端 SDK 能夠使用 reCAPTCHA:

  1. 在 Xcode 中開啟您的專案配置。

  2. 雙擊左側樹狀圖視圖中的項目名稱。

  3. “目標”部分選擇您的應用程式。

  4. 選擇資訊選項卡。

  5. 展開URL 類型部分。

  6. 點選+按鈕。

  7. URL 方案欄位中輸入反向客戶端 ID。您可以在GoogleService-Info.plist設定檔中找到該值,其形式為REVERSED_CLIENT_ID

完成後,您的配置應類似於以下內容:

客製化方案

或者,您可以自訂應用程式在顯示 reCAPTCHA 時呈現SFSafariViewControllerUIWebView的方式。為此,請建立一個符合FIRAuthUIDelegate協定的自訂類,並將其傳遞給verifyPhoneNumber:UIDelegate:completion:

選擇註冊模式

您可以選擇應用程式是否需要多重身份驗證,以及註冊用戶的方式和時間。一些常見的模式包括:

  • 將使用者的第二個因素註冊為註冊的一部分。如果您的應用程式需要對所有使用者進行多重身份驗證,請使用此方法。請注意,帳戶必須具有經過驗證的電子郵件地址才能註冊第二個因素,因此您的註冊流程必須適應這一點。

  • 提供可跳過的選項以在註冊過程中註冊第二個因素。想要鼓勵但不要求多因素身份驗證的應用程式可能會更喜歡這種方法。

  • 提供從使用者帳戶或個人資料管理頁面(而非註冊畫面)新增第二個因素的功能。這最大限度地減少了註冊過程中的摩擦,同時仍為安全敏感的用戶提供多因素身份驗證。

  • 當使用者想要存取安全要求更高的功能時,需要逐步加入第二個因素。

註冊第二個因素

若要為用戶註冊新的次要因素:

  1. 重新驗證使用者身份。

  2. 要求使用者輸入他們的電話號碼。

  3. 為用戶獲取多因素會話:

    迅速

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

    Objective-C

    [authResult.user.multiFactor
      getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session,
                                NSError * _Nullable error) {
        // ...
    }];
    
  4. 向用戶的手機發送驗證訊息。確保電話號碼的格式以+開頭,且沒有其他標點符號或空格(例如: +15105551234

    迅速

    // 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.
    }];
    

    雖然不是必需的,但最佳做法是提前通知用戶他們將收到短信,並且適用標準費率。

    verifyPhoneNumber()方法使用靜默推播通知在背景啟動應用程式驗證程序。如果靜默推播通知不可用,則會發出 reCAPTCHA 質詢。

  5. 發送簡訊代碼後,請用戶驗證該代碼。然後,使用他們的回應來建立PhoneAuthCredential

    迅速

    // 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. 初始化斷言物件:

    迅速

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    Objective-C

    FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  7. 完成報名。或者,您可以指定第二個因素的顯示名稱。這對於具有多個第二因素的使用者非常有用,因為電話號碼在身份驗證流程中被封鎖(例如,+1*****1234)。

    迅速

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

下面的程式碼顯示了註冊第二個因素的完整範例:

迅速

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

恭喜!您已成功為使用者註冊了第二個身份驗證因素。

使用第二個因素讓使用者登入

若要使用兩步驟簡訊驗證登入用戶:

  1. 使用第一個因素登入用戶,然後捕獲指示需要多因素身份驗證的錯誤。此錯誤包含解析器、有關註冊的第二個因素的提示以及證明使用者已成功通過第一個因素進行身份驗證的基礎會話。

    例如,如果使用者的第一個因素是電子郵件和密碼:

    迅速

    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取得顯示名稱。

    迅速

    // 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. 向用戶手機發送驗證簡訊:

    迅速

    // 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. 發送簡訊代碼後,請使用者驗證該代碼並使用它來建立PhoneAuthCredential

    迅速

    // 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. 使用憑證初始化斷言物件:

    迅速

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    Objective-C

    FIRMultiFactorAssertion *assertion =
        [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  6. 解決登入問題。然後,您可以存取原始登入結果,其中包括標準提供者特定的資料和身分驗證憑證:

    迅速

    // 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.
        }
    }];
    

下面的程式碼顯示了登入多因素用戶的完整範例:

迅速

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

恭喜!您使用多重身份驗證成功登入使用者。

下一步是什麼