Join us in person and online for Firebase Summit on October 18, 2022. Learn how Firebase can help you accelerate app development, release your app with confidence, and scale with ease. Register now

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

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

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

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

在你開始之前

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

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

啟用多重身份驗證

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

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

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

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

驗證您的應用

Firebase 需要驗證 SMS 請求是否來自您的應用。您可以通過兩種方式做到這一點:

  • 靜默 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
      // ...
    }
    

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

    雖然不是必需的,但最好提前通知用戶他們將收到 SMS 消息,並且適用標準費率。

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

  5. 發送 SMS 代碼後,要求用戶驗證代碼。然後,使用他們的響應來構建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) {
            // ...
        }];
    }];
}];

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

使用第二個因素讓用戶登錄

要使用雙因素 SMS 驗證登錄用戶:

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

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

    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

    迅速

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

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

下一步是什麼