在 iOS 應用程式中加入多重驗證機制

如果您已升級至 Firebase Authentication with Identity Platform,即可新增簡訊多重驗證 您的 iOS 應用程式。

多重驗證可提高應用程式安全性。雖然攻擊者 經常會竊取密碼和社群媒體帳戶、攔截簡訊, 變得更困難

事前準備

  1. 請至少啟用一個支援多重驗證的供應商。 每個供應商都支援多重驗證,但電話驗證、匿名驗證和 Apple Game Center。

  2. 確認應用程式正在驗證使用者的電子郵件地址。MFA 需要電子郵件驗證。 這可防止惡意人士利用電子郵件註冊服務 成為非擁有人,進而鎖定真正的擁有者 。

啟用多重驗證

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

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

    請一併輸入要測試應用程式的電話號碼。 雖然非必要,但我們強烈建議您註冊測試電話號碼 避免開發過程中發生節流現象

  3. 如果您尚未授權應用程式的網域,請將網域新增至允許清單 [驗證] > 中的清單設定 Firebase控制台中的頁面。

驗證應用程式

Firebase」需要驗證簡訊要求是否確實來自您的 應用程式。操作方式有以下兩種:

  • 靜音 APN 通知:使用者首次登入時, Firebase 可以將無訊息的推播通知傳送到使用者的 裝置。如果應用程式收到通知,驗證作業就會繼續。 請注意,從 iOS 8.0 開始,您不需要要求使用者允許推送 通知使用這個方法

  • reCAPTCHA 驗證:如果您無法傳送無聲通知 (例如: 例如,使用者已停用背景重新整理,或者您正在測試 匯入應用程式),您可以使用 reCAPTCHA。在許多情況下 reCAPTCHA 會自動解決,使用者完全不需要進行互動。

使用靜音通知

如何啟用 APN 通知以便與 Firebase 搭配使用:

  1. 在 Xcode 中啟用推播通知

  2. 透過 Firebase 控制台上傳 APN 驗證金鑰 (變更的內容) 會自動移轉至 Google Cloud Firebase)。 如果您沒有 APN 驗證金鑰,請參閱 使用 FCM 設定 APN 以便瞭解如何取得

    1. 開啟 Firebase 控制台

    2. 前往「專案設定」

    3. 選取「雲端通訊」分頁標籤。

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

    5. 選取金鑰。

    6. 新增該金鑰的金鑰 ID。金鑰 ID 位於 憑證、ID 與設定檔 Apple Developer Member Center

    7. 按一下 [上傳]。

如果您已有 APN 憑證,可改為上傳憑證。

使用 reCAPTCHA 驗證

如何為用戶端 SDK 啟用 reCAPTCHA:

  1. 在 Xcode 中開啟專案設定。

  2. 按一下左側樹狀檢視中的專案名稱。

  3. 從「Targets」(目標) 部分選取您的應用程式。

  4. 選取「資訊」分頁標籤。

  5. 展開「網址類型」部分。

  6. 按一下「+」按鈕。

  7. 在「網址配置」欄位中輸入撤銷的用戶端 ID。您可以 這個值列在 GoogleService-Info.plist 設定檔中 REVERSED_CLIENT_ID

完成後,設定應如下所示:

自訂配置

您可以視需求自訂應用程式呈現 SFSafariViewControllerUIWebView。待辦 建立符合 FIRAuthUIDelegate 通訊協定的自訂類別。 並傳遞至 verifyPhoneNumber:UIDelegate:completion:

選擇註冊模式

您可以選擇是否要讓應用程式需要多因素驗證,以及如何 以及註冊使用者的時間常見的模式包括:

  • 註冊使用者的第二重驗證在註冊過程中。使用這份草稿 方法。 請注意,帳戶必須擁有經過驗證的電子郵件地址才能註冊第二個帳戶 以確保註冊流程必須滿足上述需求。

  • 提供可略過的選項,讓使用者在註冊期間註冊第二個步驟。應用程式 想鼓勵但不要求進行多因素驗證時 他們偏好使用這個方法。

  • 讓使用者能夠在帳戶或設定檔中新增次要驗證條件 而非註冊畫面這可以大幅減少 同時在進行多因素驗證時 。

  • 使用者要存取應用程式時,需要逐步新增第二個驗證步驟 強化安全性要求的功能

註冊次要驗證方式

如何為使用者註冊新的次要驗證條件:

  1. 重新驗證使用者。

  2. 請使用者輸入電話號碼。

  3. 為使用者取得多重要素工作階段:

    Swift

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

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

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

    Swift

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

    Swift

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

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

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

目標-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. 透過第一個驗證方式登入使用者,然後擷取指出錯誤訊息 需要多重驗證。這項錯誤包含解析器 關於已註冊雙重驗證和基礎工作階段的提示 證明使用者已透過第一個因素成功通過驗證。

    舉例來說,如果使用者的第一個驗證方法是電子郵件地址和密碼:

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

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

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

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

    Swift

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

    Swift

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

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

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

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

恭喜!您已成功透過多重要素登入使用者 驗證。

後續步驟