為網頁應用程式新增 TOTP 多因素驗證

如果您已升級至使用 Identity Platform 的 Firebase 驗證,則可在應用程式中新增動態密碼 (TOTP) 多因素驗證 (MFA)。

透過 Identity Platform 進行 Firebase 驗證,您可以使用 TOTP 做為 MFA 的額外因素。啟用這項功能後,嘗試登入應用程式時的使用者會看到 TOTP 要求。如要產生驗證碼,應用程式必須使用能夠產生有效 TOTP 驗證碼的驗證器應用程式,例如 Google Authenticator

事前準備

  1. 請至少啟用一個支援 MFA 的提供者。請注意,除了下列支援 MFA 的所有供應商中,所有提供者均適用:

    • 電話驗證
    • 匿名驗證
    • 自訂驗證權杖
    • Apple 遊戲中心
  2. 確認您的應用程式會驗證使用者電子郵件地址。MFA 需要電子郵件驗證。這樣可以防止惡意人士使用非他們擁有的電子郵件地址註冊服務,然後再新增第二個因素,鎖定電子郵件地址的實際擁有者。

  3. 如果您尚未安裝 Firebase JavaScript SDK,請先完成安裝。

    TOTP MFA 僅適用於模組化 Web SDK 9.19.1 以上版本。

啟用 TOTP MFA

如要啟用 TOTP 做為第二因素,請使用 Admin SDK 或呼叫專案設定 REST 端點。

如要使用 Admin SDK,請按照下列步驟操作:

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

    TOTP MFA 僅適用於 Firebase Admin Node.js SDK 11.6.0 以上版本。

  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 服務設定為也接受相鄰視窗的 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:時間範圍間隔 (從 0 到 10)。預設值為 5。

    TOTP 的運作原理是確保當兩方 (驗證者和驗證工具) 於相同時間範圍內 (通常為 30 秒) 產生動態密碼時,即可產生相同的密碼。不過,為了因應各方與人為回應時間的時鐘偏移,您可以將 TOTP 服務設定為也接受相鄰視窗的 TOTP。

選擇註冊模式

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

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

  • 在註冊過程中提供可略過選項,以註冊雙重驗證。如果您要在應用程式中鼓勵 (但不要求多重驗證),可以使用此方法。

  • 讓使用者能在帳戶或設定檔管理頁面 (而非註冊畫面) 新增雙重驗證。這樣就能盡量減少註冊過程中的阻礙,同時仍為注重安全性的使用者提供多重驗證功能。

  • 如果使用者要存取強化安全性需求的功能時,必須逐步新增第二重驗證條件。

為使用者註冊 TOTP MFA

啟用 TOTP MFA 做為應用程式的第二因素後,請實作用戶端邏輯,以將使用者註冊至 TOTP MFA:

  1. 匯入必要的 MFA 類別和函式:

    import {
      multiFactor,
      TotpMultiFactorGenerator,
      TotpSecret,
      getAuth,
    } from "firebase/auth";
    
  2. 重新驗證使用者。

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

    // Generate a TOTP secret.
    const multiFactorSession = await multiFactor(currentUser).getSession();
    const totpSecret = await TotpMultiFactorGenerator.generateSecret(
      multiFactorSession
    );
    
  4. 向使用者顯示密鑰,並提示他們在驗證器應用程式中輸入密鑰。

    在使用多個驗證器應用程式的情況下,使用者可以掃描代表 Google Authenticator 相容金鑰 URI 的 QR code,藉此快速新增 TOTP 密鑰。如要產生此用途的 QR code,請使用 generateQrCodeUrl() 產生 URI,然後使用您選擇的 QR code 程式庫進行編碼。例如:

    const totpUri = totpSecret.generateQrCodeUrl(
        currentUser.email,
        "Your App's Name"
    );
    await QRExampleLib.toCanvas(totpUri, qrElement);
    

    無論是否顯示 QR code,一律顯示密鑰,以便支援無法讀取 QR code 的驗證器應用程式:

    // Also display this key:
    const secret = totpSecret.secretKey;
    

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

  5. 提示使用者輸入驗證器應用程式上顯示的 TOTP,並使用該 TTTP 完成 MFA 註冊:

    // Ask the user for a verification code from the authenticator app.
    const verificationCode = // Code from user input.
    
    // Finalize the enrollment.
    const multiFactorAssertion = TotpMultiFactorGenerator.assertionForEnrollment(
      totpSecret,
      verificationCode
    );
    await multiFactor(currentUser).enroll(multiFactorAssertion, mfaDisplayName);
    

透過第二重驗證方式登入使用者

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

  1. 匯入必要的 MFA 類別和函式:

    import {
        getAuth,
        getMultiFactorResolver,
        TotpMultiFactorGenerator,
    } from "firebase/auth";
    
  2. 按照未使用 MFA 的方式呼叫其中一個 signInWith 方法。(例如 signInWithEmailAndPassword())。如果方法擲回 auth/multi-factor-auth-required 錯誤,請啟動應用程式的 MFA 流程。

    try {
        const userCredential = await signInWithEmailAndPassword(
            getAuth(),
            email,
            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 (error) {
        switch (error.code) {
            case "auth/multi-factor-auth-required":
                // Initiate your second factor sign-in flow. (See next step.)
                // ...
                break;
            case ...:  // Handle other errors, such as wrong passwords.
                break;
        }
    }
    
  3. 應用程式的 MFA 流程應先提示使用者選擇要使用的第二個因素。如要取得支援的第二個因素清單,請查看 MultiFactorResolver 執行個體的 hints 屬性:

    const mfaResolver = getMultiFactorResolver(getAuth(), error);
    const enrolledFactors = mfaResolver.hints.map(info => info.displayName);
    
  4. 如果使用者選擇使用 TOTP,請提示他們輸入驗證器應用程式上顯示的 TOTP,並使用它登入:

    switch (mfaResolver.hints[selectedIndex].factorId) {
        case TotpMultiFactorGenerator.FACTOR_ID:
            const otpFromAuthenticator = // OTP typed by the user.
            const multiFactorAssertion =
                TotpMultiFactorGenerator.assertionForSignIn(
                    mfaResolver.hints[selectedIndex].uid,
                    otpFromAuthenticator
                );
            try {
                const userCredential = await mfaResolver.resolveSignIn(
                    multiFactorAssertion
                );
                // Successfully signed in!
            } catch (error) {
                // Invalid or expired OTP.
            }
            break;
        case PhoneMultiFactorGenerator.FACTOR_ID:
            // Handle SMS second factor.
            break;
        default:
            // Unsupported second factor?
            break;
    }
    

取消註冊 TOTP MFA

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

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

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

import {
    EmailAuthProvider,
    TotpMultiFactorGenerator,
    getAuth,
    multiFactor,
    reauthenticateWithCredential,
} from "firebase/auth";

try {
    // Unenroll from TOTP MFA.
    await multiFactor(currentUser).unenroll(mfaEnrollmentId);
} catch  (error) {
    if (error.code === 'auth/user-token-expired') {
        // 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.

        const credential = EmailAuthProvider.credential(email, password);
        await reauthenticateWithCredential(
            currentUser,
            credential
        );
    }
}

後續步驟