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

如果您已升級至 Firebase Authentication with Identity Platform,可以為應用程式新增限時動態密碼 (TOTP) 多重驗證 (MFA)。

Firebase Authentication with Identity Platform 可讓您使用 TOTP 做為 MFA 的額外因素。啟用這項功能後,使用者嘗試登入應用程式時,就會看到 TOTP 的要求。如要產生此類驗證碼,使用者必須使用可產生有效 TOTP 驗證碼的驗證程式應用程式,例如 Google Authenticator

事前準備

  1. 請啟用至少一個支援 MFA 的供應商。請注意,除了以下供應商以外,所有供應商都支援多重認證:

    • 電話驗證
    • 匿名驗證
    • 自訂驗證權杖
    • Apple Game Center
  2. 請確認您的應用程式會驗證使用者的電子郵件地址。多重驗證功能需要通過電子郵件驗證。這麼做可防止惡意人士使用不屬於自己的電子郵件地址註冊服務,然後透過新增第二個因素,將電子郵件地址的實際擁有者鎖在門外。

  3. 如果您尚未安裝 Firebase Apple SDK,請先安裝。

    二階段驗證碼 MFA 僅支援 Apple SDK 10.12.0 以上版本,且僅適用於 iOS。

啟用 TOTP MFA

如要啟用 TOTP 做為次要驗證方法,請使用 Admin SDK 或呼叫專案設定 REST 端點。

如要使用 Admin SDK,請執行下列操作:

  1. 如果您尚未安裝 Firebase Admin Node.js SDK,請先安裝。

    Firebase Admin Node.js SDK 11.6.0 以上版本僅支援 TOTP MFA。

  2. 執行以下指令:

    import { getAuth } from 'firebase-admin/auth';
    
    getAuth().projectConfigManager().updateProjectConfig(
    {
          multiFactorConfig: {
              providerConfigs: [{
                  state: "ENABLED",
                  totpProviderConfig: {
                      adjacentIntervals: NUM_ADJ_INTERVALS
                  }
              }]
          }
    })
    

    更改下列內容:

    • NUM_ADJ_INTERVALS:接受 TOTP 的相鄰時間範圍間隔數,從零到十。預設值為 5。

      TOTP 的運作方式是確保雙方 (驗證者和驗證者) 在相同的時間範圍內 (通常為 30 秒) 產生 OTP,產生的密碼都相同。不過,為因應各方之間的時間差異和人為回應時間,您可以將 TOTP 服務設為也接受相鄰視窗的 TOTP。

如要使用 REST API 啟用 TOTP MFA,請執行以下操作:

curl -X PATCH "https://identitytoolkit.googleapis.com/admin/v2/projects/PROJECT_ID/config?updateMask=mfa" \
    -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    -H "Content-Type: application/json" \
    -H "X-Goog-User-Project: PROJECT_ID" \
    -d \
    '{
        "mfa": {
          "providerConfigs": [{
            "state": "ENABLED",
            "totpProviderConfig": {
              "adjacentIntervals": NUM_ADJ_INTERVALS
            }
          }]
       }
    }'

更改下列內容:

  • PROJECT_ID:專案 ID。
  • NUM_ADJ_INTERVALS:時間區間間隔數量,從零到十。預設值為 5。

    TOTP 的運作方式是確保雙方 (驗證者和驗證者) 在相同的時間範圍內 (通常為 30 秒) 產生 OTP,產生的密碼都相同。不過,為了配合各方與人為回應時間之間的時鐘偏差,您可以將 TOTP 服務設定為也接受相鄰視窗的 TOTP。

選擇註冊模式

您可以選擇應用程式是否需要多重驗證,以及註冊使用者的方式和時間。以下列舉幾個常見的模式:

  • 在註冊過程中註冊使用者的第二重驗證。如果應用程式需要所有使用者的多重驗證,請使用這個方法。

  • 提供可略過的選項,讓使用者在註冊時註冊第二重驗證。如果您想在應用程式中鼓勵使用多重驗證,但不強制要求使用,可以採用這種做法。

  • 能夠從使用者帳戶或設定檔管理頁面 (而非註冊畫面) 新增第二重驗證方式。這樣可盡量減少註冊過程中的阻礙,同時讓有安全性考量的使用者可以使用多重要素驗證功能。

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

讓使用者註冊 TOTP 多重驗證

將 TOTP 多重驗證機制設為應用程式的第二個驗證機制後,請實作用戶端邏輯,讓使用者註冊 TOTP 多重驗證機制:

  1. 重新驗證使用者。

  2. 為已驗證的使用者產生 TOTP 密鑰:

    // Generate a TOTP secret.
    guard let mfaSession = try? await currentUser.multiFactor.session() else { return }
    guard let totpSecret = try? await TOTPMultiFactorGenerator.generateSecret(with: mfaSession) else { return }
    
    // Display the secret to the user and prompt them to enter it into their
    // authenticator app. (See the next step.)
    
  3. 向使用者顯示密鑰,並提示他們輸入密鑰至驗證器應用程式:

    // Display this key:
    let secret = totpSecret.sharedSecretKey()
    

    除了顯示密鑰,您也可以嘗試自動將其新增至裝置的預設驗證器應用程式。方法是產生與 Google Authenticator 相容的金鑰 URI,並傳送至 openInOTPApp(withQRCodeURL:)

    let otpAuthUri = totpSecret.generateQRCodeURL(
        withAccountName: currentUser.email ?? "default account",
        issuer: "Your App Name")
    totpSecret.openInOTPApp(withQRCodeURL: otpAuthUri)
    

    使用者將秘密金鑰新增至驗證器應用程式後,系統就會開始產生 TOTP。

  4. 提示使用者輸入 Authenticator 應用程式顯示的 TOTP,並使用該值完成多因素驗證註冊程序:

    // Ask the user for a verification code from the authenticator app.
    let verificationCode = // Code from user input.
    
    // Finalize the enrollment.
    let multiFactorAssertion = TOTPMultiFactorGenerator.assertionForEnrollment(
        with: totpSecret,
        oneTimePassword: verificationCode)
    do {
        try await currentUser.multiFactor.enroll(
            with: multiFactorAssertion,
            displayName: "TOTP")
    } catch {
        // Wrong or expired OTP. Re-prompt the user.
    }
    

透過雙重驗證登入使用者

如要使用 TOTP 多重驗證登入使用者,請使用下列程式碼:

  1. 如同未使用 MFA 時一樣,呼叫其中一個 signIn(with...:) 方法 (例如 signIn(withEmail:password:))。如果方法擲回錯誤代碼 secondFactorRequired,請啟動應用程式的 MFA 流程。

    do {
        let authResult = try await Auth.auth().signIn(withEmail: email, password: password)
    
        // If the user is not enrolled with a second factor and provided valid
        // credentials, sign-in succeeds.
    
        // (If your app requires MFA, this could be considered an error
        // condition, which you would resolve by forcing the user to enroll a
        // second factor.)
    
        // ...
    } catch let error as AuthErrorCode where error.code == .secondFactorRequired {
        // Initiate your second factor sign-in flow. (See next step.)
        // ...
    } catch {
        // Other auth error.
        throw error
    }
    
  2. 應用程式的 MFA 流程應先提示使用者選擇要使用的次要驗證方式。您可以查看 MultiFactorResolver 例項的 hints 屬性,取得支援的第二種驗證方式清單:

    let mfaKey = AuthErrorUserInfoMultiFactorResolverKey
    guard let resolver = error.userInfo[mfaKey] as? MultiFactorResolver else { return }
    let enrolledFactors = resolver.hints.map(\.displayName)
    
  3. 如果使用者選擇使用 TOTP,請提示他們輸入 Authenticator 應用程式顯示的 TOTP,並使用該代碼登入:

    let multiFactorInfo = resolver.hints[selectedIndex]
    switch multiFactorInfo.factorID {
    case TOTPMultiFactorID:
        let otpFromAuthenticator = // OTP typed by the user.
        let assertion = TOTPMultiFactorGenerator.assertionForSignIn(
            withEnrollmentID: multiFactorInfo.uid,
            oneTimePassword: otpFromAuthenticator)
        do {
            let authResult = try await resolver.resolveSignIn(with: assertion)
        } catch {
            // Wrong or expired OTP. Re-prompt the user.
        }
    default:
        return
    }
    

取消註冊 TOTP 多重驗證

本節說明如何處理使用者取消註冊 TOTP MFA 的情況。

如果使用者已註冊多個多重驗證選項,且從最近啟用的選項取消註冊,系統會傳送 auth/user-token-expired 並登出使用者。使用者必須再次登入並驗證現有憑證,例如電子郵件地址和密碼。

如要取消註冊使用者、處理錯誤,並觸發重新驗證程序,請使用下列程式碼:

guard let currentUser = Auth.auth().currentUser else { return }

// Prompt the user to select a factor to unenroll, from this array:
currentUser.multiFactor.enrolledFactors

// ...

// Unenroll the second factor.
let multiFactorInfo = currentUser.multiFactor.enrolledFactors[selectedIndex]
do {
    try await currentUser.multiFactor.unenroll(with: multiFactorInfo)
} catch let error as AuthErrorCode where error.code == .invalidUserToken {
    // Second factor unenrolled, but the user was signed out. Re-authenticate
    // them.
}

後續步驟