Catch up on highlights from Firebase at Google I/O 2023. Learn more

向您的 iOS 應用程序添加多因素身份驗證

如果您已使用 Identity Platform 升級到 Firebase 身份驗證,則可以將 SMS 多重身份驗證添加到您的 iOS 應用程序。

多重身份驗證可提高應用程序的安全性。雖然攻擊者通常會洩露密碼和社交帳戶,但攔截短信卻更加困難。

在你開始之前

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

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

啟用多重身份驗證

  1. 打開 Firebase 控制台的身份驗證 > 登錄方法頁面。

  2. Advanced部分,啟用SMS Multi-factor Authentication

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

  3. 如果您尚未授權應用的域,請將其添加到 Firebase 控制台的“身份驗證”>“設置”頁面上的允許列表。

驗證您的應用

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

  • 靜默 APNs 通知:當您首次登錄用戶時,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 authentication key下,在iOS app configuration部分,單擊Upload

    5. 選擇你的鑰匙。

    6. 添加密鑰的密鑰 ID。您可以在Apple Developer Member CenterCertificates, Identifiers & Profiles下找到密鑰 ID。

    7. 單擊上傳

如果您已經擁有 APNs 證書,則可以改為上傳證書。

使用 reCAPTCHA 驗證

啟用客戶端 SDK 以使用 reCAPTCHA:

  1. 在 Xcode 中打開您的項目配置。

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

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

  4. 選擇信息選項卡。

  5. 展開URL 類型部分。

  6. 單擊+按鈕。

  7. URL Schemes字段中輸入您的反向客戶端 ID。您可以在GoogleService-Info.plist配置文件中找到該值作為REVERSED_CLIENT_ID列出。

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

自定義方案

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

選擇註冊模式

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

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

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

  • 提供從用戶帳戶或個人資料管理頁面而不是註冊屏幕添加第二因素的能力。這最大限度地減少了註冊過程中的摩擦,同時仍然為對安全敏感的用戶提供多因素身份驗證。

  • 當用戶想要訪問具有更高安全要求的功能時,需要逐漸添加第二個因素。

註冊第二個因素

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

  1. 重新驗證用戶。

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

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

    迅速

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

    目標-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.
    }
    

    目標-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. 發送短信代碼後,要求用戶驗證代碼。然後,使用他們的響應來構建PhoneAuthCredential

    迅速

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

    目標-C

    // Ask user for the SMS verification code. Then:
    FIRPhoneAuthCredential *credential = [FIRPhoneAuthProvider.provider
                                           credentialWithVerificationID:verificationID
                                           verificationCode:kPhoneSecondFactorVerificationCode];
    
  6. 初始化斷言對象:

    迅速

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    目標-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
      // ...
    }
    

    目標-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
      // ...
    }
  }
})

目標-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.
      }
    }
    

    目標-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].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 {
      // Unsupported second factor.
      // Note that only phone second factors are currently supported.
    }
    

    目標-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 {
      // Unsupported second factor.
      // Note that only phone second factors are currently supported.
    }
    
  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.
    }
    

    目標-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)
    

    目標-C

    // Ask user for the SMS verification code. Then:
    FIRPhoneAuthCredential *credential =
        [FIRPhoneAuthProvider.provider
          credentialWithVerificationID:verificationID
                      verificationCode:verificationCodeFromUser];
    
  5. 使用憑證初始化斷言對象:

    迅速

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    目標-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.
    }
    

    目標-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.
        }
      }
    }
  }
}

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

恭喜!您已使用多重身份驗證成功登錄用戶。

下一步是什麼