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

如果您已透過 Identity Platform 升級至 Firebase 驗證,可以新增有時限的動態密碼 (TOTP) 應用程式的多因素驗證 (MFA)。

採用 Identity Platform 的 Firebase 驗證之後,你可以使用 TOTP 做為 MFA 的附加因素。當您 啟用這項功能後,如果使用者嘗試登入您的應用程式,就會看到 失誤如要產生這組 ID,必須使用驗證器應用程式 有效的 TOTP 驗證碼,例如 Google Authenticator

事前準備

  1. 請至少啟用一個支援 MFA 的供應商。請注意,所有供應商 以下支援 MFA:

    • 電話驗證
    • 匿名驗證
    • 自訂驗證權杖
    • Apple Game Center
  2. 請確認應用程式會驗證使用者的電子郵件地址。MFA 需要電子郵件 驗證。這可防止惡意人士註冊服務 然後鎖在他人的電子郵件地址 新增第二個步驟,藉此取得電子郵件地址擁有者的資訊。

  3. 如果您還沒有安裝 Firebase Android SDK

    只有 Android SDK 22.1.0 版 以上。

啟用 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 的時間範圍間隔 (介於 0 至 10 之間)。 預設值為五。

      TOTP 成功的關鍵在於確保 驗證工具) 在同一時間範圍內 (通常是 30 秒) 產生動態密碼 系統產生的密碼相同但是,為了配合時鐘 偏差值,您可以設定 TOTP 服務也支援來自鄰近視窗的 TOTPs。

如要使用 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:時間範圍數量 介於 0 到 10 個之間的間隔預設值為五。

    TOTP 成功的關鍵在於確保 驗證工具) 在同一時間範圍內 (通常是 30 秒) 產生動態密碼 系統產生的密碼相同但是,為了配合時鐘 偏差值,您可以設定 TOTP 服務也支援來自鄰近視窗的 TOTPs。

選擇註冊模式

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

  • 註冊使用者的第二重驗證在註冊過程中。使用這份草稿 方法。

  • 提供可略過的選項,讓使用者在註冊期間註冊第二個步驟。如果發生以下情況: 如果想鼓勵應用程式也不需要多因素驗證 這種做法

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

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

為使用者註冊 TOTP MFA

為應用程式啟用 TOTP MFA 做為次要驗證方式後,請實作用戶端 邏輯運算子,為使用者註冊 TOTP MFA:

  1. 重新驗證使用者。

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

    // Generate a TOTP secret.
    Firebase.auth.currentUser.multiFactor.session
        .addOnSuccessListener { multiFactorSession ->
            TotpMultiFactorGenerator.generateSecret(multiFactorSession)
                .addOnSuccessListener { totpSecret ->
                    // Display the secret to the user and prompt them to
                    // enter it into their authenticator app. (See the next
                    // step.)
                }
        }
    
  3. 向使用者顯示這組密鑰,並提示他們在 驗證器應用程式:

    // Display this key:
    val secret = totpSecret.sharedSecretKey
    

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

    val qrCodeUri = totpSecret.generateQrCodeUrl(
        currentUser.email ?: "default account",
        "Your App Name")
    totpSecret.openInOtpApp(qrCodeUri)
    

    使用者將密鑰新增至驗證器應用程式後,系統就會啟動 。

  4. 提示使用者輸入驗證器應用程式顯示的 TOTP,並 請使用它完成 MFA 註冊:

    // Ask the user for a verification code from the authenticator app.
    val verificationCode = // Code from user input.
    
    // Finalize the enrollment.
    val multiFactorAssertion = TotpMultiFactorGenerator
        .getAssertionForEnrollment(totpSecret, verificationCode)
    Firebase.auth.currentUser.multiFactor.enroll(multiFactorAssertion, "TOTP")
        .addOnSuccessListener {
            // Enrollment complete.
        }
    

透過雙重驗證登入使用者

如要透過 TOTP MFA 登入使用者,請使用以下代碼:

  1. 呼叫其中一種 signInWith 方法,方法與您不使用 MFA 時相同。 (例如 signInWithEmailAndPassword())。如果方法擲回 FirebaseAuthMultiFactorException,請啟動應用程式的 MFA 流程。

    Firebase.auth.signInWithEmailAndPassword(email, password)
        .addOnSuccessListener { result ->
            // 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.)
    
            // ...
        }
        .addOnFailureListener { exception ->
            when (exception) {
                is FirebaseAuthMultiFactorException -> {
                    // Initiate your second factor sign-in flow. (See next step.)
                    // ...
                }
            }
        }
    
  2. 應用程式的 MFA 流程應先提示使用者選擇第二個驗證步驟 要使用的應用程式如要查看支援的雙重驗證清單,請前往 檢查 MultiFactorResolver 執行個體的 hints 屬性:

    val enrolledFactors = exception.resolver.hints.map { it.displayName }
    
  3. 如果使用者選擇使用 TOTP,請提示他們輸入 並用來登入:

    when (exception.resolver.hints[selectedIndex].factorId) {
        TotpMultiFactorGenerator.FACTOR_ID -> {
            val otpFromAuthenticator = // OTP typed by the user.
            val assertion = TotpMultiFactorGenerator.getAssertionForSignIn(
                exception.resolver.hints[selectedIndex].uid,
                otpFromAuthenticator
            )
            exception.resolver.resolveSignIn(assertion)
                .addOnSuccessListener { result ->
                    // Successfully signed in!
                }
                .addOnFailureListener { resolveError ->
                    // Invalid or expired OTP.
                }
        }
        PhoneMultiFactorGenerator.FACTOR_ID -> {
            // Handle SMS second factor.
        }
    }
    

取消註冊 TOTP MFA

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

如果使用者已註冊多個 MFA 選項,且取消註冊了該選項 來自最近啟用的選項,會收到auth/user-token-expired 且已登出。使用者必須再次登入並驗證 現有憑證—例如電子郵件地址和密碼。

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

Firebase.auth.currentUser.multiFactor.unenroll(mfaEnrollmentId)
    .addOnSuccessListener {
        // Second factor unenrolled.
    }
    .addOnFailureListener { exception ->
        when (exception) {
            is FirebaseAuthInvalidUserException -> {
                // Second factor unenrolled. If the user was signed out, re-authenticate
                // them.

                // For example, if they signed in with a password, prompt them to
                // provide it again, then call `reauthenticateWithCredential()` as shown
                // below.
                val credential = EmailAuthProvider.getCredential(email, password)
                currentUser.reauthenticate(credential)
                    .addOnSuccessListener { 
                        // Success!
                    }
                    .addOnFailureListener { 
                        // Bad email address and password combination.
                    }
            }
        }
    }

後續步驟