在 Android 應用程式中加入 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 Android SDK,請先安裝。

    只有 Android SDK 22.1.0 以上版本支援 TOTP MFA。

啟用 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 之間。預設值為 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.
    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. 向使用者顯示密鑰,並提示他們輸入密鑰至 authenticator 應用程式:

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

    除了顯示密鑰之外,您也可以嘗試自動將密鑰新增至裝置的預設 authenticator 應用程式。如要這麼做,請產生 Google Authenticator 相容的金鑰 URI,然後將其傳遞至 openInOtpApp()

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

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

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

    // 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 多重驗證登入使用者,請使用下列程式碼:

  1. 如同未使用多重身份驗證 (MFA) 一樣,呼叫其中一個 signInWith 方法。(例如 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,請提示他們輸入 Authenticator 應用程式顯示的 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 多重驗證

本節說明如何處理使用者取消註冊 TOTP 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.
                    }
            }
        }
    }

後續步驟